#include "HierarchyPanelSupport.h" namespace XCEngine::UI::Editor::App { using namespace HierarchyPanelSupport; std::vector HierarchyPanel::BuildInteractionInputEvents( const std::vector& inputEvents, const UIRect& bounds, bool allowInteraction, bool panelActive) const { const std::vector rawEvents = FilterHierarchyInputEvents( bounds, inputEvents, allowInteraction, panelActive, HasActivePointerCapture()); struct DragPreviewState { std::string armedItemId = {}; UIPoint pressPosition = {}; bool armed = false; bool dragging = false; }; const Widgets::UIEditorTreeViewLayout layout = m_treeFrame.layout.bounds.width > 0.0f ? m_treeFrame.layout : Widgets::BuildUIEditorTreeViewLayout( bounds, m_treeItems, m_expansion, BuildEditorTreeViewMetrics()); DragPreviewState preview = {}; preview.armed = m_dragState.armed; preview.armedItemId = m_dragState.armedItemId; preview.pressPosition = m_dragState.pressPosition; preview.dragging = m_dragState.dragging; std::vector filteredEvents = {}; filteredEvents.reserve(rawEvents.size()); for (const UIInputEvent& event : rawEvents) { bool suppress = false; switch (event.type) { case UIInputEventType::PointerButtonDown: if (event.pointerButton == UIPointerButton::Left) { UIEditorTreeViewHitTarget hitTarget = {}; const Widgets::UIEditorTreeViewItem* hitItem = ResolveHitItem(layout, m_treeItems, event.position, &hitTarget); if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { preview.armed = true; preview.armedItemId = hitItem->itemId; preview.pressPosition = event.position; } else { preview.armed = false; preview.armedItemId.clear(); } } if (preview.dragging) { suppress = true; } break; case UIInputEventType::PointerMove: if (preview.dragging) { suppress = true; break; } if (preview.armed && ComputeSquaredDistance(event.position, preview.pressPosition) >= kDragThreshold * kDragThreshold) { preview.dragging = true; suppress = true; } break; case UIInputEventType::PointerButtonUp: if (event.pointerButton == UIPointerButton::Left) { if (preview.dragging) { suppress = true; preview.dragging = false; } preview.armed = false; preview.armedItemId.clear(); } else if (preview.dragging) { suppress = true; } break; case UIInputEventType::PointerLeave: if (preview.dragging) { suppress = true; } break; case UIInputEventType::FocusLost: preview.armed = false; preview.dragging = false; preview.armedItemId.clear(); break; default: break; } if (!suppress) { filteredEvents.push_back(event); } } return filteredEvents; } void HierarchyPanel::ProcessDragAndFrameEvents( const std::vector& inputEvents, const UIRect& bounds, bool allowInteraction, bool panelActive) { const std::vector filteredEvents = FilterHierarchyInputEvents( bounds, inputEvents, allowInteraction, panelActive, HasActivePointerCapture()); if (m_treeFrame.result.selectionChanged) { EmitSelectionEvent(); } if (m_treeFrame.result.renameRequested && !m_treeFrame.result.renameItemId.empty()) { Event event = {}; event.kind = EventKind::RenameRequested; event.itemId = m_treeFrame.result.renameItemId; if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { event.label = node->label; } m_frameEvents.push_back(std::move(event)); } for (const UIInputEvent& event : filteredEvents) { switch (event.type) { case UIInputEventType::PointerButtonDown: if (event.pointerButton == UIPointerButton::Left) { UIEditorTreeViewHitTarget hitTarget = {}; const Widgets::UIEditorTreeViewItem* hitItem = ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { m_dragState.armed = true; m_dragState.armedItemId = hitItem->itemId; m_dragState.pressPosition = event.position; } else { m_dragState.armed = false; m_dragState.armedItemId.clear(); } } break; case UIInputEventType::PointerMove: if (m_dragState.armed && !m_dragState.dragging && ComputeSquaredDistance(event.position, m_dragState.pressPosition) >= kDragThreshold * kDragThreshold) { m_dragState.dragging = !m_dragState.armedItemId.empty(); m_dragState.draggedItemId = m_dragState.armedItemId; m_dragState.dropTargetItemId.clear(); m_dragState.dropToRoot = false; m_dragState.validDropTarget = false; if (m_dragState.dragging) { m_dragState.requestPointerCapture = true; if (!m_selection.IsSelected(m_dragState.draggedItemId)) { m_selection.SetSelection(m_dragState.draggedItemId); EmitSelectionEvent(); } } } if (m_dragState.dragging) { UIEditorTreeViewHitTarget hitTarget = {}; const Widgets::UIEditorTreeViewItem* hitItem = ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); m_dragState.dropTargetItemId.clear(); m_dragState.dropToRoot = false; m_dragState.validDropTarget = false; if (hitItem != nullptr && (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { m_dragState.dropTargetItemId = hitItem->itemId; m_dragState.validDropTarget = m_model.CanReparent(m_dragState.draggedItemId, m_dragState.dropTargetItemId); } else if (ContainsPoint(bounds, event.position)) { m_dragState.dropToRoot = true; m_dragState.validDropTarget = m_model.GetParentId(m_dragState.draggedItemId).has_value(); } } break; case UIInputEventType::PointerButtonUp: if (event.pointerButton != UIPointerButton::Left) { break; } if (m_dragState.dragging) { if (m_dragState.validDropTarget) { const std::string draggedItemId = m_dragState.draggedItemId; const std::string dropTargetItemId = m_dragState.dropTargetItemId; const bool changed = m_dragState.dropToRoot ? m_model.MoveToRoot(draggedItemId) : m_model.Reparent(draggedItemId, dropTargetItemId); if (changed) { RebuildItems(); EmitReparentEvent( m_dragState.dropToRoot ? EventKind::MovedToRoot : EventKind::Reparented, draggedItemId, dropTargetItemId); } } m_dragState.armed = false; m_dragState.dragging = false; m_dragState.armedItemId.clear(); m_dragState.draggedItemId.clear(); m_dragState.dropTargetItemId.clear(); m_dragState.dropToRoot = false; m_dragState.validDropTarget = false; m_dragState.requestPointerRelease = true; } else { m_dragState.armed = false; m_dragState.armedItemId.clear(); } break; case UIInputEventType::FocusLost: if (m_dragState.dragging) { m_dragState.requestPointerRelease = true; } m_dragState = {}; break; default: break; } } } void HierarchyPanel::Update( const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector& inputEvents, bool allowInteraction, bool panelActive) { ResetTransientState(); const UIEditorPanelContentHostPanelState* panelState = FindMountedHierarchyPanel(contentHostFrame); if (panelState == nullptr) { m_visible = false; m_treeFrame = {}; m_dragState = {}; return; } if (m_treeItems.empty()) { RebuildItems(); } m_visible = true; const std::vector interactionEvents = BuildInteractionInputEvents( inputEvents, panelState->bounds, allowInteraction, panelActive); m_treeFrame = UpdateUIEditorTreeViewInteraction( m_treeInteractionState, m_selection, m_expansion, panelState->bounds, m_treeItems, interactionEvents, BuildEditorTreeViewMetrics()); ProcessDragAndFrameEvents( inputEvents, panelState->bounds, allowInteraction, panelActive); } } // namespace XCEngine::UI::Editor::App