2026-04-15 19:30:58 +08:00
|
|
|
#include "Docking/DockHostInteractionInternal.h"
|
2026-04-15 11:23:39 +08:00
|
|
|
#include <algorithm>
|
|
|
|
|
#include <string_view>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2026-04-15 19:30:58 +08:00
|
|
|
namespace XCEngine::UI::Editor::Internal {
|
2026-04-15 11:23:39 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-04-15 19:30:58 +08:00
|
|
|
bool ShouldUseDockHostPointerPosition(const UIInputEvent& event) {
|
2026-04-15 11:23:39 +08:00
|
|
|
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<UIEditorTabStripItem> BuildTabStripItems(
|
|
|
|
|
const UIEditorDockHostTabStackLayout& tabStack) {
|
|
|
|
|
std::vector<UIEditorTabStripItem> 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<UIEditorTabStripItem> 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 19:30:58 +08:00
|
|
|
} // namespace XCEngine::UI::Editor::Internal
|