#pragma once #include #include #include #include #include #include namespace XCEngine::UI::Editor::Collections::TreeDragDrop { using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; using DragDropInteraction::kDefaultDragThreshold; using Widgets::HitTestUIEditorTreeView; using Widgets::UIEditorTreeViewHitTarget; using Widgets::UIEditorTreeViewHitTargetKind; using Widgets::UIEditorTreeViewInvalidIndex; struct State : DragDropInteraction::State { bool dropToRoot = false; }; struct ProcessResult : DragDropInteraction::ProcessResult { bool droppedToRoot = false; }; inline void ResetTransientRequests(State& state) { DragDropInteraction::ResetTransientRequests(state); } inline bool HasActivePointerCapture(const State& state) { return DragDropInteraction::HasActivePointerCapture(state); } inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) { return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; } inline const Widgets::UIEditorTreeViewItem* ResolveHitItem( const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, const UIPoint& point, UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) { const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); if (hitTargetOutput != nullptr) { *hitTargetOutput = hitTarget; } if (hitTarget.itemIndex >= items.size()) { return nullptr; } return &items[hitTarget.itemIndex]; } inline std::size_t FindVisibleIndexForItemId( const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, std::string_view itemId) { for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { return visibleIndex; } } return UIEditorTreeViewInvalidIndex; } inline std::vector BuildInteractionInputEvents( const State& state, const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, const std::vector& rawEvents, float dragThreshold = kDefaultDragThreshold) { auto resolveDraggableItem = [&layout, &items](const UIPoint& point) -> std::string { UIEditorTreeViewHitTarget hitTarget = {}; const Widgets::UIEditorTreeViewItem* hitItem = ResolveHitItem(layout, items, point, &hitTarget); if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { return hitItem->itemId; } return {}; }; DragDropInteraction::PreviewState preview = DragDropInteraction::BuildPreviewState(state); std::vector filteredEvents = {}; filteredEvents.reserve(rawEvents.size()); for (const DragDropInteraction::UIInputEvent& event : rawEvents) { if (!DragDropInteraction::ProcessPreviewEvent( preview, event, resolveDraggableItem, dragThreshold)) { filteredEvents.push_back(event); } } return filteredEvents; } template ProcessResult ProcessInputEvents( State& state, const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, const std::vector& inputEvents, const UIRect& bounds, Callbacks& callbacks, float dragThreshold = kDefaultDragThreshold) { struct AdaptedCallbacks { const Widgets::UIEditorTreeViewLayout& layout; const std::vector& items; const UIRect& bounds; Callbacks& callbacks; bool droppedToRoot = false; std::string ResolveDraggableItem(const UIPoint& point) const { UIEditorTreeViewHitTarget hitTarget = {}; const Widgets::UIEditorTreeViewItem* hitItem = ResolveHitItem(layout, items, point, &hitTarget); if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { return hitItem->itemId; } return {}; } void ResetDropTarget(State& state) const { state.dropTargetItemId.clear(); state.dropToRoot = false; state.validDropTarget = false; } void UpdateDropTarget( State& state, std::string_view draggedItemId, const UIPoint& point) const { UIEditorTreeViewHitTarget hitTarget = {}; const Widgets::UIEditorTreeViewItem* hitItem = ResolveHitItem(layout, items, point, &hitTarget); if (hitItem != nullptr && (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { state.dropTargetItemId = hitItem->itemId; state.validDropTarget = callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId); return; } if (ContainsPoint(bounds, point)) { state.dropToRoot = true; state.validDropTarget = callbacks.CanDropToRoot(draggedItemId); } } bool IsItemSelected(std::string_view itemId) const { return callbacks.IsItemSelected(itemId); } bool SelectDraggedItem(std::string_view itemId) const { return callbacks.SelectDraggedItem(itemId); } bool CommitDrop(State& state, DragDropInteraction::ProcessResult&) { droppedToRoot = state.dropToRoot; return state.dropToRoot ? callbacks.CommitDropToRoot(state.draggedItemId) : callbacks.CommitDropOnItem( state.draggedItemId, state.dropTargetItemId); } } adaptedCallbacks{ layout, items, bounds, callbacks }; ProcessResult result = {}; const DragDropInteraction::ProcessResult interactionResult = DragDropInteraction::ProcessInputEvents( state, inputEvents, adaptedCallbacks, dragThreshold); result.selectionForced = interactionResult.selectionForced; result.dropCommitted = interactionResult.dropCommitted; result.draggedItemId = interactionResult.draggedItemId; result.dropTargetItemId = interactionResult.dropTargetItemId; result.droppedToRoot = adaptedCallbacks.droppedToRoot; return result; } } // namespace XCEngine::UI::Editor::Collections::TreeDragDrop