From 4fa178cfd15f16f408d1157434c6d34d0044cc0e Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 15 Apr 2026 11:23:39 +0800 Subject: [PATCH] refactor(new_editor/docking): split dock host interaction helpers --- new_editor/CMakeLists.txt | 1 + .../Docking/UIEditorDockHostInteraction.cpp | 562 ++---------------- .../UIEditorDockHostInteractionHelpers.cpp | 460 ++++++++++++++ .../UIEditorDockHostInteractionInternal.h | 77 +++ 4 files changed, 588 insertions(+), 512 deletions(-) create mode 100644 new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp create mode 100644 new_editor/src/Docking/UIEditorDockHostInteractionInternal.h diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 178977d7..c085448e 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -70,6 +70,7 @@ set(XCUI_EDITOR_DOCKING_SOURCES src/Docking/UIEditorDockHostHitTest.cpp src/Docking/UIEditorDockHostRendering.cpp src/Docking/UIEditorDockHostInteraction.cpp + src/Docking/UIEditorDockHostInteractionHelpers.cpp ) set(XCUI_EDITOR_MENU_SOURCES diff --git a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp index 7cc8f330..3357aa70 100644 --- a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp +++ b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp @@ -1,13 +1,11 @@ -#include +#include "Docking/UIEditorDockHostInteractionInternal.h" + #include #include -#include #include -#include #include -#include namespace XCEngine::UI::Editor { @@ -15,482 +13,23 @@ namespace { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; using ::XCEngine::UI::Widgets::BeginUISplitterDrag; using ::XCEngine::UI::Widgets::EndUISplitterDrag; using ::XCEngine::UI::Widgets::UpdateUISplitterDrag; using Widgets::BuildUIEditorDockHostLayout; using Widgets::FindUIEditorDockHostSplitterLayout; -using Widgets::HitTestUIEditorDockHost; using Widgets::UIEditorDockHostHitTarget; using Widgets::UIEditorDockHostHitTargetKind; -using Widgets::UIEditorDockHostTabItemLayout; -using Widgets::UIEditorDockHostTabStackLayout; -using Widgets::UIEditorTabStripHitTargetKind; -using Widgets::UIEditorTabStripItem; - -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 = {}; - UIEditorDockHostHitTarget hitTarget = {}; - int priority = 0; -}; - -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - return true; - default: - return false; - } -} - -bool ShouldDispatchTabStripEvent( - const UIInputEvent& event, - bool splitterActive) { - if (splitterActive && event.type != UIInputEventType::FocusLost) { - return false; - } - - switch (event.type) { - case UIInputEventType::FocusLost: - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerLeave: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::KeyDown: - return true; - default: - return false; - } -} - -UIEditorWorkspaceLayoutOperationResult ApplySplitRatio( - UIEditorWorkspaceController& controller, - std::string_view nodeId, - float splitRatio) { - return controller.SetSplitRatio(nodeId, splitRatio); -} - -UIEditorWorkspaceCommandResult DispatchPanelCommand( - UIEditorWorkspaceController& controller, - UIEditorWorkspaceCommandKind kind, - std::string panelId) { - UIEditorWorkspaceCommand command = {}; - command.kind = kind; - command.panelId = std::move(panelId); - return controller.Dispatch(command); -} - -UIEditorDockHostTabStripInteractionEntry& FindOrCreateTabStripInteractionEntry( - UIEditorDockHostInteractionState& state, - std::string_view nodeId) { - for (UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { - if (entry.nodeId == nodeId) { - return entry; - } - } - - state.tabStripInteractions.push_back({}); - UIEditorDockHostTabStripInteractionEntry& entry = state.tabStripInteractions.back(); - entry.nodeId = std::string(nodeId); - return entry; -} - -void PruneTabStripInteractionEntries( - UIEditorDockHostInteractionState& state, - const Widgets::UIEditorDockHostLayout& layout) { - const auto isVisibleNodeId = [&layout](std::string_view nodeId) { - return std::find_if( - layout.tabStacks.begin(), - layout.tabStacks.end(), - [nodeId](const UIEditorDockHostTabStackLayout& tabStack) { - return tabStack.nodeId == nodeId; - }) != layout.tabStacks.end(); - }; - - state.tabStripInteractions.erase( - std::remove_if( - state.tabStripInteractions.begin(), - state.tabStripInteractions.end(), - [&isVisibleNodeId](const UIEditorDockHostTabStripInteractionEntry& entry) { - return !isVisibleNodeId(entry.nodeId); - }), - state.tabStripInteractions.end()); - - state.dockHostState.tabStripStates.erase( - std::remove_if( - state.dockHostState.tabStripStates.begin(), - state.dockHostState.tabStripStates.end(), - [&isVisibleNodeId](const Widgets::UIEditorDockHostTabStripVisualState& entry) { - return !isVisibleNodeId(entry.nodeId); - }), - state.dockHostState.tabStripStates.end()); - - if (!state.activeTabDragNodeId.empty() && - !isVisibleNodeId(state.activeTabDragNodeId)) { - state.activeTabDragNodeId.clear(); - state.activeTabDragPanelId.clear(); - state.dockHostState.dropPreview = {}; - } -} - -void SyncDockHostTabStripVisualStates(UIEditorDockHostInteractionState& state) { - state.dockHostState.tabStripStates.clear(); - state.dockHostState.tabStripStates.reserve(state.tabStripInteractions.size()); - for (const UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { - Widgets::UIEditorDockHostTabStripVisualState visualState = {}; - visualState.nodeId = entry.nodeId; - visualState.state = entry.state.tabStripState; - state.dockHostState.tabStripStates.push_back(std::move(visualState)); - } -} - -bool HasFocusedTabStrip(const UIEditorDockHostInteractionState& state) { - return std::find_if( - state.tabStripInteractions.begin(), - state.tabStripInteractions.end(), - [](const UIEditorDockHostTabStripInteractionEntry& entry) { - return entry.state.tabStripState.focused; - }) != state.tabStripInteractions.end(); -} - -const UIEditorDockHostTabStackLayout* FindTabStackLayoutByNodeId( - const Widgets::UIEditorDockHostLayout& layout, - std::string_view nodeId) { - for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (tabStack.nodeId == nodeId) { - return &tabStack; - } - } - - return nullptr; -} - -bool IsPointInsideRect( - 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; -} - -void ClearAllTabStripTransientInteractions( - UIEditorDockHostInteractionState& state) { - for (UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { - ClearUIEditorTabStripTransientInteraction(entry.state); - } - SyncDockHostTabStripVisualStates(state); -} - -void ClearTabDockDragState(UIEditorDockHostInteractionState& state) { - state.activeTabDragNodeId.clear(); - state.activeTabDragPanelId.clear(); - state.dockHostState.dropPreview = {}; -} - -std::vector BuildTabStripItems( - const UIEditorDockHostTabStackLayout& tabStack) { - std::vector items = {}; - items.reserve(tabStack.items.size()); - for (const UIEditorDockHostTabItemLayout& itemLayout : tabStack.items) { - UIEditorTabStripItem item = {}; - item.tabId = itemLayout.panelId; - item.title = itemLayout.title; - item.closable = itemLayout.closable; - items.push_back(std::move(item)); - } - - return items; -} - -UIEditorDockHostHitTarget MapTabStripHitTarget( - const UIEditorDockHostTabStackLayout& tabStack, - const UIEditorTabStripInteractionResult& result) { - UIEditorDockHostHitTarget target = {}; - target.nodeId = tabStack.nodeId; - target.index = result.hitTarget.index; - - switch (result.hitTarget.kind) { - case UIEditorTabStripHitTargetKind::HeaderBackground: - target.kind = UIEditorDockHostHitTargetKind::TabStripBackground; - break; - case UIEditorTabStripHitTargetKind::Tab: - target.kind = UIEditorDockHostHitTargetKind::Tab; - if (result.hitTarget.index < tabStack.items.size()) { - 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; - } - - return target; -} - -int ResolveTabStripPriority(const UIEditorTabStripInteractionResult& result) { - if (result.reorderRequested || - result.dragStarted || - result.dragEnded || - result.dragCanceled) { - return 5; - } - - if (result.closeRequested) { - return 4; - } - - if (result.selectionChanged || result.keyboardNavigated) { - return 3; - } - - if (result.consumed || result.hitTarget.kind != UIEditorTabStripHitTargetKind::None) { - return 2; - } - - return 0; -} - -DockHostTabStripEventResult ProcessTabStripEvent( - UIEditorDockHostInteractionState& state, - const Widgets::UIEditorDockHostLayout& layout, - const UIInputEvent& event, - const Widgets::UIEditorDockHostMetrics& metrics) { - DockHostTabStripEventResult resolved = {}; - - for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (!state.activeTabDragNodeId.empty() && - tabStack.nodeId != state.activeTabDragNodeId) { - continue; - } - - UIEditorDockHostTabStripInteractionEntry& entry = - FindOrCreateTabStripInteractionEntry(state, tabStack.nodeId); - std::string selectedTabId = tabStack.selectedPanelId; - const std::vector items = BuildTabStripItems(tabStack); - const UIEditorTabStripInteractionFrame frame = UpdateUIEditorTabStripInteraction( - entry.state, - selectedTabId, - tabStack.bounds, - items, - { event }, - metrics.tabStripMetrics); - - const int priority = ResolveTabStripPriority(frame.result); - if (priority < resolved.priority) { - continue; - } - - resolved.nodeId = tabStack.nodeId; - resolved.hitTarget = MapTabStripHitTarget(tabStack, frame.result); - resolved.requestPointerCapture = frame.result.requestPointerCapture; - resolved.releasePointerCapture = frame.result.releasePointerCapture; - 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 && - !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() - ? frame.result.selectedTabId - : resolved.hitTarget.panelId; - } else if (priority == 0) { - continue; - } else { - resolved.commandRequested = false; - resolved.reorderRequested = false; - resolved.panelId.clear(); - } - - resolved.consumed = frame.result.consumed; - resolved.priority = priority; - } - - SyncDockHostTabStripVisualStates(state); - return resolved; -} - -std::size_t ResolveTabHeaderDropInsertionIndex( - const UIEditorDockHostTabStackLayout& tabStack, - const UIPoint& point) { - 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; -} - -UIEditorWorkspaceDockPlacement ResolveDockPlacement( - const UIEditorDockHostTabStackLayout& tabStack, - const UIPoint& point) { - 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; -} - -void SyncDockPreview( - UIEditorDockHostInteractionState& state, - const Widgets::UIEditorDockHostLayout& layout) { - state.dockHostState.dropPreview = {}; - if (state.activeTabDragNodeId.empty() || - state.activeTabDragPanelId.empty() || - !state.hasPointerPosition) { - return; - } - - for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (!IsPointInsideRect(tabStack.bounds, state.pointerPosition)) { - continue; - } - - const UIEditorWorkspaceDockPlacement placement = - ResolveDockPlacement(tabStack, state.pointerPosition); - if (tabStack.nodeId == state.activeTabDragNodeId && - placement == UIEditorWorkspaceDockPlacement::Center) { - return; - } - - if (tabStack.nodeId == state.activeTabDragNodeId && - tabStack.items.size() <= 1u) { - return; - } - - Widgets::UIEditorDockHostDropPreviewState preview = {}; - preview.visible = true; - preview.sourceNodeId = state.activeTabDragNodeId; - preview.sourcePanelId = state.activeTabDragPanelId; - preview.targetNodeId = tabStack.nodeId; - preview.placement = placement; - if (placement == UIEditorWorkspaceDockPlacement::Center) { - preview.insertionIndex = - ResolveTabHeaderDropInsertionIndex(tabStack, state.pointerPosition); - if (preview.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { - preview.insertionIndex = tabStack.items.size(); - } - } - state.dockHostState.dropPreview = std::move(preview); - return; - } -} - -void SyncHoverTarget( - UIEditorDockHostInteractionState& state, - const Widgets::UIEditorDockHostLayout& layout) { - if (state.splitterDragState.active) { - state.dockHostState.hoveredTarget = { - UIEditorDockHostHitTargetKind::SplitterHandle, - state.dockHostState.activeSplitterNodeId, - {}, - Widgets::UIEditorTabStripInvalidIndex - }; - return; - } - - if (!state.activeTabDragNodeId.empty()) { - state.dockHostState.hoveredTarget = {}; - return; - } - - if (!state.hasPointerPosition) { - state.dockHostState.hoveredTarget = {}; - return; - } - - state.dockHostState.hoveredTarget = - HitTestUIEditorDockHost(layout, state.pointerPosition); -} } // namespace UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( UIEditorDockHostInteractionState& state, UIEditorWorkspaceController& controller, - const UIRect& bounds, + const ::XCEngine::UI::UIRect& bounds, const std::vector& inputEvents, const Widgets::UIEditorDockHostMetrics& metrics) { - SyncDockHostTabStripVisualStates(state); + Detail::SyncDockHostTabStripVisualStates(state); Widgets::UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -498,8 +37,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - PruneTabStripInteractionEntries(state, layout); - SyncDockHostTabStripVisualStates(state); + Detail::PruneTabStripInteractionEntries(state, layout); + Detail::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -507,11 +46,11 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - SyncHoverTarget(state, layout); + Detail::SyncHoverTarget(state, layout); UIEditorDockHostInteractionResult interactionResult = {}; for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { + if (Detail::ShouldUsePointerPosition(event)) { state.pointerPosition = event.position; state.hasPointerPosition = true; } else if (event.type == UIInputEventType::PointerLeave) { @@ -528,11 +67,11 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( pointerHitTarget.kind == UIEditorDockHostHitTargetKind::SplitterHandle; UIEditorDockHostInteractionResult eventResult = {}; - const DockHostTabStripEventResult tabStripResult = + const Detail::DockHostTabStripEventResult tabStripResult = !splitterOwnsPointerDown && - ShouldDispatchTabStripEvent(event, state.splitterDragState.active) - ? ProcessTabStripEvent(state, layout, event, metrics) - : DockHostTabStripEventResult {}; + Detail::ShouldDispatchTabStripEvent(event, state.splitterDragState.active) + ? Detail::ProcessTabStripEvent(state, layout, event, metrics) + : Detail::DockHostTabStripEventResult {}; eventResult.requestPointerCapture = tabStripResult.requestPointerCapture; eventResult.releasePointerCapture = tabStripResult.releasePointerCapture; if (!tabStripResult.draggedTabId.empty() && @@ -542,9 +81,9 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (!state.activeTabDragNodeId.empty() && !state.activeTabDragPanelId.empty() && !state.splitterDragState.active) { - SyncDockPreview(state, layout); + Detail::SyncDockPreview(state, layout); } else if (event.type == UIInputEventType::PointerLeave || - state.activeTabDragNodeId.empty()) { + state.activeTabDragNodeId.empty()) { state.dockHostState.dropPreview = {}; } @@ -559,12 +98,12 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (state.splitterDragState.active) { EndUISplitterDrag(state.splitterDragState); state.dockHostState.activeSplitterNodeId.clear(); - ClearAllTabStripTransientInteractions(state); + Detail::ClearAllTabStripTransientInteractions(state); eventResult.consumed = true; eventResult.releasePointerCapture = true; } if (!state.activeTabDragNodeId.empty() || tabStripResult.dragCanceled) { - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); eventResult.consumed = true; eventResult.releasePointerCapture = eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; @@ -583,7 +122,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.splitterDragState, state.pointerPosition, draggedLayout)) { - eventResult.layoutResult = ApplySplitRatio( + eventResult.layoutResult = Detail::ApplySplitRatio( controller, state.dockHostState.activeSplitterNodeId, draggedLayout.splitRatio); @@ -601,10 +140,10 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (tabStripResult.dragStarted) { state.activeTabDragNodeId = tabStripResult.nodeId; state.activeTabDragPanelId = tabStripResult.draggedTabId; - SyncDockPreview(state, layout); + Detail::SyncDockPreview(state, layout); } if (tabStripResult.dragEnded || tabStripResult.dragCanceled) { - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); } if (eventResult.consumed || eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None) { @@ -618,7 +157,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.dockHostState.hoveredTarget = {}; } state.dockHostState.dropPreview = {}; - if (!HasFocusedTabStrip(state)) { + if (!Detail::HasFocusedTabStrip(state)) { state.dockHostState.focused = false; } break; @@ -645,7 +184,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( splitter->metrics, state.pointerPosition, state.splitterDragState)) { - ClearAllTabStripTransientInteractions(state); + Detail::ClearAllTabStripTransientInteractions(state); state.dockHostState.activeSplitterNodeId = splitter->nodeId; state.dockHostState.focused = true; eventResult.consumed = true; @@ -682,7 +221,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.splitterDragState, state.pointerPosition, draggedLayout)) { - eventResult.layoutResult = ApplySplitRatio( + eventResult.layoutResult = Detail::ApplySplitRatio( controller, state.dockHostState.activeSplitterNodeId, draggedLayout.splitRatio); @@ -691,7 +230,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( UIEditorWorkspaceLayoutOperationStatus::Changed; } EndUISplitterDrag(state.splitterDragState); - ClearAllTabStripTransientInteractions(state); + Detail::ClearAllTabStripTransientInteractions(state); eventResult.consumed = true; eventResult.releasePointerCapture = true; eventResult.activeSplitterNodeId = state.dockHostState.activeSplitterNodeId; @@ -719,7 +258,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( UIEditorWorkspaceLayoutOperationStatus::Changed; eventResult.consumed = true; eventResult.hitTarget = tabStripResult.hitTarget; - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } @@ -741,8 +280,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (preview.placement == UIEditorWorkspaceDockPlacement::Center) { std::size_t insertionIndex = preview.insertionIndex; if (insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { - if (const UIEditorDockHostTabStackLayout* targetTabStack = - FindTabStackLayoutByNodeId(layout, preview.targetNodeId); + if (const auto* targetTabStack = + Detail::FindTabStackLayoutByNodeId(layout, preview.targetNodeId); targetTabStack != nullptr) { insertionIndex = targetTabStack->items.size(); } else { @@ -768,15 +307,14 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( AppendUIEditorRuntimeTrace( "dock", "drop result status=" + - std::string( - GetUIEditorWorkspaceLayoutOperationStatusName( - eventResult.layoutResult.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; - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } @@ -784,14 +322,14 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (!state.activeTabDragNodeId.empty() && !state.activeTabDragPanelId.empty() && state.hasPointerPosition && - !IsPointInsideRect(layout.bounds, state.pointerPosition)) { + !Detail::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; - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } @@ -799,13 +337,13 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (tabStripResult.dragEnded || tabStripResult.dragCanceled) { eventResult.consumed = tabStripResult.consumed; eventResult.hitTarget = tabStripResult.hitTarget; - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { - eventResult.commandResult = DispatchPanelCommand( + eventResult.commandResult = Detail::DispatchPanelCommand( controller, tabStripResult.commandKind, tabStripResult.panelId); @@ -832,7 +370,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( case UIEditorDockHostHitTargetKind::PanelHeader: case UIEditorDockHostHitTargetKind::PanelBody: case UIEditorDockHostHitTargetKind::PanelFooter: - eventResult.commandResult = DispatchPanelCommand( + eventResult.commandResult = Detail::DispatchPanelCommand( controller, UIEditorWorkspaceCommandKind::ActivatePanel, state.dockHostState.hoveredTarget.panelId); @@ -844,7 +382,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( break; case UIEditorDockHostHitTargetKind::PanelCloseButton: - eventResult.commandResult = DispatchPanelCommand( + eventResult.commandResult = Detail::DispatchPanelCommand( controller, UIEditorWorkspaceCommandKind::ClosePanel, state.dockHostState.hoveredTarget.panelId); @@ -859,14 +397,13 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( case UIEditorDockHostHitTargetKind::TabCloseButton: case UIEditorDockHostHitTargetKind::TabStripBackground: state.dockHostState.focused = true; - eventResult.consumed = tabStripResult.priority > 0 - ? tabStripResult.consumed - : true; + eventResult.consumed = + tabStripResult.priority > 0 ? tabStripResult.consumed : true; break; case UIEditorDockHostHitTargetKind::None: default: - if (!HasFocusedTabStrip(state)) { + if (!Detail::HasFocusedTabStrip(state)) { state.dockHostState.focused = false; } break; @@ -877,13 +414,13 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( if (tabStripResult.dragCanceled) { eventResult.consumed = true; eventResult.hitTarget = tabStripResult.hitTarget; - ClearTabDockDragState(state); + Detail::ClearTabDockDragState(state); state.dockHostState.focused = true; break; } if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { - eventResult.commandResult = DispatchPanelCommand( + eventResult.commandResult = Detail::DispatchPanelCommand( controller, tabStripResult.commandKind, tabStripResult.panelId); @@ -903,7 +440,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( break; } - SyncDockHostTabStripVisualStates(state); + Detail::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -911,8 +448,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - PruneTabStripInteractionEntries(state, layout); - SyncDockHostTabStripVisualStates(state); + Detail::PruneTabStripInteractionEntries(state, layout); + Detail::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -920,7 +457,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - SyncHoverTarget(state, layout); + Detail::SyncHoverTarget(state, layout); if (eventResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { eventResult.hitTarget = state.dockHostState.hoveredTarget; } @@ -930,14 +467,15 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( eventResult.layoutChanged || eventResult.requestPointerCapture || eventResult.releasePointerCapture || - eventResult.layoutResult.status != UIEditorWorkspaceLayoutOperationStatus::Rejected || + eventResult.layoutResult.status != + UIEditorWorkspaceLayoutOperationStatus::Rejected || eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None || !eventResult.activeSplitterNodeId.empty()) { interactionResult = std::move(eventResult); } } - SyncDockHostTabStripVisualStates(state); + Detail::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -945,8 +483,8 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - PruneTabStripInteractionEntries(state, layout); - SyncDockHostTabStripVisualStates(state); + Detail::PruneTabStripInteractionEntries(state, layout); + Detail::SyncDockHostTabStripVisualStates(state); layout = BuildUIEditorDockHostLayout( bounds, controller.GetPanelRegistry(), @@ -954,7 +492,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( controller.GetSession(), state.dockHostState, metrics); - SyncHoverTarget(state, layout); + Detail::SyncHoverTarget(state, layout); if (interactionResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { interactionResult.hitTarget = state.dockHostState.hoveredTarget; } diff --git a/new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp b/new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp new file mode 100644 index 00000000..3a4324e9 --- /dev/null +++ b/new_editor/src/Docking/UIEditorDockHostInteractionHelpers.cpp @@ -0,0 +1,460 @@ +#include "Docking/UIEditorDockHostInteractionInternal.h" + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Detail { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using Widgets::HitTestUIEditorDockHost; +using Widgets::UIEditorDockHostHitTarget; +using Widgets::UIEditorDockHostHitTargetKind; +using Widgets::UIEditorDockHostTabItemLayout; +using Widgets::UIEditorDockHostTabStackLayout; +using Widgets::UIEditorTabStripHitTargetKind; +using Widgets::UIEditorTabStripItem; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool ShouldDispatchTabStripEvent( + const UIInputEvent& event, + bool splitterActive) { + if (splitterActive && event.type != UIInputEventType::FocusLost) { + return false; + } + + switch (event.type) { + case UIInputEventType::FocusLost: + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::KeyDown: + return true; + default: + return false; + } +} + +UIEditorWorkspaceLayoutOperationResult ApplySplitRatio( + UIEditorWorkspaceController& controller, + std::string_view nodeId, + float splitRatio) { + return controller.SetSplitRatio(nodeId, splitRatio); +} + +UIEditorWorkspaceCommandResult DispatchPanelCommand( + UIEditorWorkspaceController& controller, + UIEditorWorkspaceCommandKind kind, + std::string panelId) { + UIEditorWorkspaceCommand command = {}; + command.kind = kind; + command.panelId = std::move(panelId); + return controller.Dispatch(command); +} + +UIEditorDockHostTabStripInteractionEntry& FindOrCreateTabStripInteractionEntry( + UIEditorDockHostInteractionState& state, + std::string_view nodeId) { + for (UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { + if (entry.nodeId == nodeId) { + return entry; + } + } + + state.tabStripInteractions.push_back({}); + UIEditorDockHostTabStripInteractionEntry& entry = state.tabStripInteractions.back(); + entry.nodeId = std::string(nodeId); + return entry; +} + +void PruneTabStripInteractionEntries( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout) { + const auto isVisibleNodeId = [&layout](std::string_view nodeId) { + return std::find_if( + layout.tabStacks.begin(), + layout.tabStacks.end(), + [nodeId](const UIEditorDockHostTabStackLayout& tabStack) { + return tabStack.nodeId == nodeId; + }) != layout.tabStacks.end(); + }; + + state.tabStripInteractions.erase( + std::remove_if( + state.tabStripInteractions.begin(), + state.tabStripInteractions.end(), + [&isVisibleNodeId](const UIEditorDockHostTabStripInteractionEntry& entry) { + return !isVisibleNodeId(entry.nodeId); + }), + state.tabStripInteractions.end()); + + state.dockHostState.tabStripStates.erase( + std::remove_if( + state.dockHostState.tabStripStates.begin(), + state.dockHostState.tabStripStates.end(), + [&isVisibleNodeId](const Widgets::UIEditorDockHostTabStripVisualState& entry) { + return !isVisibleNodeId(entry.nodeId); + }), + state.dockHostState.tabStripStates.end()); + + if (!state.activeTabDragNodeId.empty() && + !isVisibleNodeId(state.activeTabDragNodeId)) { + state.activeTabDragNodeId.clear(); + state.activeTabDragPanelId.clear(); + state.dockHostState.dropPreview = {}; + } +} + +void SyncDockHostTabStripVisualStates(UIEditorDockHostInteractionState& state) { + state.dockHostState.tabStripStates.clear(); + state.dockHostState.tabStripStates.reserve(state.tabStripInteractions.size()); + for (const UIEditorDockHostTabStripInteractionEntry& entry : + state.tabStripInteractions) { + Widgets::UIEditorDockHostTabStripVisualState visualState = {}; + visualState.nodeId = entry.nodeId; + visualState.state = entry.state.tabStripState; + state.dockHostState.tabStripStates.push_back(std::move(visualState)); + } +} + +bool HasFocusedTabStrip(const UIEditorDockHostInteractionState& state) { + return std::find_if( + state.tabStripInteractions.begin(), + state.tabStripInteractions.end(), + [](const UIEditorDockHostTabStripInteractionEntry& entry) { + return entry.state.tabStripState.focused; + }) != state.tabStripInteractions.end(); +} + +const UIEditorDockHostTabStackLayout* FindTabStackLayoutByNodeId( + const Widgets::UIEditorDockHostLayout& layout, + std::string_view nodeId) { + for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (tabStack.nodeId == nodeId) { + return &tabStack; + } + } + + return nullptr; +} + +bool IsPointInsideRect( + 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; +} + +void ClearAllTabStripTransientInteractions(UIEditorDockHostInteractionState& state) { + for (UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { + ClearUIEditorTabStripTransientInteraction(entry.state); + } + SyncDockHostTabStripVisualStates(state); +} + +void ClearTabDockDragState(UIEditorDockHostInteractionState& state) { + state.activeTabDragNodeId.clear(); + state.activeTabDragPanelId.clear(); + state.dockHostState.dropPreview = {}; +} + +std::vector BuildTabStripItems( + const UIEditorDockHostTabStackLayout& tabStack) { + std::vector items = {}; + items.reserve(tabStack.items.size()); + for (const UIEditorDockHostTabItemLayout& itemLayout : tabStack.items) { + UIEditorTabStripItem item = {}; + item.tabId = itemLayout.panelId; + item.title = itemLayout.title; + item.closable = itemLayout.closable; + items.push_back(std::move(item)); + } + + return items; +} + +UIEditorDockHostHitTarget MapTabStripHitTarget( + const UIEditorDockHostTabStackLayout& tabStack, + const UIEditorTabStripInteractionResult& result) { + UIEditorDockHostHitTarget target = {}; + target.nodeId = tabStack.nodeId; + target.index = result.hitTarget.index; + + switch (result.hitTarget.kind) { + case UIEditorTabStripHitTargetKind::HeaderBackground: + target.kind = UIEditorDockHostHitTargetKind::TabStripBackground; + break; + case UIEditorTabStripHitTargetKind::Tab: + target.kind = UIEditorDockHostHitTargetKind::Tab; + if (result.hitTarget.index < tabStack.items.size()) { + 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; + } + + return target; +} + +int ResolveTabStripPriority(const UIEditorTabStripInteractionResult& result) { + if (result.reorderRequested || + result.dragStarted || + result.dragEnded || + result.dragCanceled) { + return 5; + } + + if (result.closeRequested) { + return 4; + } + + if (result.selectionChanged || result.keyboardNavigated) { + return 3; + } + + if (result.consumed || + result.hitTarget.kind != UIEditorTabStripHitTargetKind::None) { + return 2; + } + + return 0; +} + +DockHostTabStripEventResult ProcessTabStripEvent( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout, + const UIInputEvent& event, + const Widgets::UIEditorDockHostMetrics& metrics) { + DockHostTabStripEventResult resolved = {}; + + for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (!state.activeTabDragNodeId.empty() && + tabStack.nodeId != state.activeTabDragNodeId) { + continue; + } + + UIEditorDockHostTabStripInteractionEntry& entry = + FindOrCreateTabStripInteractionEntry(state, tabStack.nodeId); + std::string selectedTabId = tabStack.selectedPanelId; + const std::vector items = BuildTabStripItems(tabStack); + const UIEditorTabStripInteractionFrame frame = UpdateUIEditorTabStripInteraction( + entry.state, + selectedTabId, + tabStack.bounds, + items, + { event }, + metrics.tabStripMetrics); + + const int priority = ResolveTabStripPriority(frame.result); + if (priority < resolved.priority) { + continue; + } + + resolved.nodeId = tabStack.nodeId; + resolved.hitTarget = MapTabStripHitTarget(tabStack, frame.result); + resolved.requestPointerCapture = frame.result.requestPointerCapture; + resolved.releasePointerCapture = frame.result.releasePointerCapture; + 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 && + !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() + ? frame.result.selectedTabId + : resolved.hitTarget.panelId; + } else if (priority == 0) { + continue; + } else { + resolved.commandRequested = false; + resolved.reorderRequested = false; + resolved.panelId.clear(); + } + + resolved.consumed = frame.result.consumed; + resolved.priority = priority; + } + + SyncDockHostTabStripVisualStates(state); + return resolved; +} + +std::size_t ResolveTabHeaderDropInsertionIndex( + const UIEditorDockHostTabStackLayout& tabStack, + const UIPoint& point) { + 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; +} + +UIEditorWorkspaceDockPlacement ResolveDockPlacement( + const UIEditorDockHostTabStackLayout& tabStack, + const UIPoint& point) { + 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; +} + +void SyncDockPreview( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout) { + state.dockHostState.dropPreview = {}; + if (state.activeTabDragNodeId.empty() || + state.activeTabDragPanelId.empty() || + !state.hasPointerPosition) { + return; + } + + for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (!IsPointInsideRect(tabStack.bounds, state.pointerPosition)) { + continue; + } + + const UIEditorWorkspaceDockPlacement placement = + ResolveDockPlacement(tabStack, state.pointerPosition); + if (tabStack.nodeId == state.activeTabDragNodeId && + placement == UIEditorWorkspaceDockPlacement::Center) { + return; + } + + if (tabStack.nodeId == state.activeTabDragNodeId && + tabStack.items.size() <= 1u) { + return; + } + + Widgets::UIEditorDockHostDropPreviewState preview = {}; + preview.visible = true; + preview.sourceNodeId = state.activeTabDragNodeId; + preview.sourcePanelId = state.activeTabDragPanelId; + preview.targetNodeId = tabStack.nodeId; + preview.placement = placement; + if (placement == UIEditorWorkspaceDockPlacement::Center) { + preview.insertionIndex = + ResolveTabHeaderDropInsertionIndex(tabStack, state.pointerPosition); + if (preview.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { + preview.insertionIndex = tabStack.items.size(); + } + } + state.dockHostState.dropPreview = std::move(preview); + return; + } +} + +void SyncHoverTarget( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout) { + if (state.splitterDragState.active) { + state.dockHostState.hoveredTarget = { + UIEditorDockHostHitTargetKind::SplitterHandle, + state.dockHostState.activeSplitterNodeId, + {}, + Widgets::UIEditorTabStripInvalidIndex + }; + return; + } + + if (!state.activeTabDragNodeId.empty()) { + state.dockHostState.hoveredTarget = {}; + return; + } + + if (!state.hasPointerPosition) { + state.dockHostState.hoveredTarget = {}; + return; + } + + state.dockHostState.hoveredTarget = + HitTestUIEditorDockHost(layout, state.pointerPosition); +} + +} // namespace XCEngine::UI::Editor::Detail diff --git a/new_editor/src/Docking/UIEditorDockHostInteractionInternal.h b/new_editor/src/Docking/UIEditorDockHostInteractionInternal.h new file mode 100644 index 00000000..59a0a590 --- /dev/null +++ b/new_editor/src/Docking/UIEditorDockHostInteractionInternal.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::Detail { + +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 = {}; + Widgets::UIEditorDockHostHitTarget hitTarget = {}; + int priority = 0; +}; + +bool ShouldUsePointerPosition(const ::XCEngine::UI::UIInputEvent& event); +bool ShouldDispatchTabStripEvent( + const ::XCEngine::UI::UIInputEvent& event, + bool splitterActive); +UIEditorWorkspaceLayoutOperationResult ApplySplitRatio( + UIEditorWorkspaceController& controller, + std::string_view nodeId, + float splitRatio); +UIEditorWorkspaceCommandResult DispatchPanelCommand( + UIEditorWorkspaceController& controller, + UIEditorWorkspaceCommandKind kind, + std::string panelId); +UIEditorDockHostTabStripInteractionEntry& FindOrCreateTabStripInteractionEntry( + UIEditorDockHostInteractionState& state, + std::string_view nodeId); +void PruneTabStripInteractionEntries( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout); +void SyncDockHostTabStripVisualStates(UIEditorDockHostInteractionState& state); +bool HasFocusedTabStrip(const UIEditorDockHostInteractionState& state); +const Widgets::UIEditorDockHostTabStackLayout* FindTabStackLayoutByNodeId( + const Widgets::UIEditorDockHostLayout& layout, + std::string_view nodeId); +bool IsPointInsideRect( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIPoint& point); +void ClearAllTabStripTransientInteractions(UIEditorDockHostInteractionState& state); +void ClearTabDockDragState(UIEditorDockHostInteractionState& state); +std::vector BuildTabStripItems( + const Widgets::UIEditorDockHostTabStackLayout& tabStack); +Widgets::UIEditorDockHostHitTarget MapTabStripHitTarget( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const UIEditorTabStripInteractionResult& result); +int ResolveTabStripPriority(const UIEditorTabStripInteractionResult& result); +DockHostTabStripEventResult ProcessTabStripEvent( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout, + const ::XCEngine::UI::UIInputEvent& event, + const Widgets::UIEditorDockHostMetrics& metrics); +std::size_t ResolveTabHeaderDropInsertionIndex( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const ::XCEngine::UI::UIPoint& point); +UIEditorWorkspaceDockPlacement ResolveDockPlacement( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const ::XCEngine::UI::UIPoint& point); +void SyncDockPreview( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout); +void SyncHoverTarget( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout); + +} // namespace XCEngine::UI::Editor::Detail