#pragma once #include #include #include namespace XCEngine::UI::Editor::Collections::DragDropInteraction { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIPointerButton; inline constexpr float kDefaultDragThreshold = 4.0f; struct State { std::string armedItemId = {}; std::string draggedItemId = {}; std::string dropTargetItemId = {}; UIPoint pressPosition = {}; bool armed = false; bool dragging = false; bool validDropTarget = false; bool requestPointerCapture = false; bool requestPointerRelease = false; }; struct PreviewState { std::string armedItemId = {}; UIPoint pressPosition = {}; bool armed = false; bool dragging = false; }; struct ProcessResult { bool selectionForced = false; bool dropCommitted = false; std::string draggedItemId = {}; std::string dropTargetItemId = {}; }; template inline void ResetTransientRequests(StateLike& state) { state.requestPointerCapture = false; state.requestPointerRelease = false; } template inline bool HasActivePointerCapture(const StateLike& state) { return state.dragging; } inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { const float dx = lhs.x - rhs.x; const float dy = lhs.y - rhs.y; return dx * dx + dy * dy; } template inline PreviewState BuildPreviewState(const StateLike& state) { PreviewState preview = {}; preview.armedItemId = state.armedItemId; preview.pressPosition = state.pressPosition; preview.armed = state.armed; preview.dragging = state.dragging; return preview; } template inline bool ProcessPreviewEvent( PreviewState& preview, const UIInputEvent& event, ResolveDraggableItem&& resolveDraggableItem, float dragThreshold = kDefaultDragThreshold) { bool suppress = false; switch (event.type) { case UIInputEventType::PointerButtonDown: if (event.pointerButton == UIPointerButton::Left) { preview.armedItemId = resolveDraggableItem(event.position); preview.pressPosition = event.position; preview.armed = !preview.armedItemId.empty(); if (!preview.armed) { 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) >= dragThreshold * dragThreshold) { preview.dragging = !preview.armedItemId.empty(); suppress = preview.dragging; } 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: if (preview.dragging && (event.type == UIInputEventType::PointerWheel || event.type == UIInputEventType::PointerEnter)) { suppress = true; } break; } return suppress; } template inline void ResetInteractionSession(StateLike& state, Callbacks& callbacks) { state.armed = false; state.dragging = false; state.armedItemId.clear(); state.draggedItemId.clear(); callbacks.ResetDropTarget(state); } template ProcessResult ProcessInputEvents( StateLike& state, const std::vector& inputEvents, Callbacks& callbacks, float dragThreshold = kDefaultDragThreshold) { ProcessResult result = {}; for (const UIInputEvent& event : inputEvents) { switch (event.type) { case UIInputEventType::PointerButtonDown: if (event.pointerButton == UIPointerButton::Left) { state.draggedItemId.clear(); callbacks.ResetDropTarget(state); state.armedItemId = callbacks.ResolveDraggableItem(event.position); state.pressPosition = event.position; state.armed = !state.armedItemId.empty(); if (!state.armed) { state.armedItemId.clear(); } } break; case UIInputEventType::PointerMove: if (state.armed && !state.dragging && ComputeSquaredDistance(event.position, state.pressPosition) >= dragThreshold * dragThreshold) { state.dragging = !state.armedItemId.empty(); state.draggedItemId = state.armedItemId; callbacks.ResetDropTarget(state); if (state.dragging) { state.requestPointerCapture = true; if (!callbacks.IsItemSelected(state.draggedItemId)) { result.selectionForced = callbacks.SelectDraggedItem(state.draggedItemId); } } } if (state.dragging) { callbacks.ResetDropTarget(state); callbacks.UpdateDropTarget(state, state.draggedItemId, event.position); } break; case UIInputEventType::PointerButtonUp: if (event.pointerButton != UIPointerButton::Left) { break; } if (state.dragging) { if (state.validDropTarget) { result.draggedItemId = state.draggedItemId; result.dropTargetItemId = state.dropTargetItemId; result.dropCommitted = callbacks.CommitDrop(state, result); } ResetInteractionSession(state, callbacks); state.requestPointerRelease = true; } else { state.armed = false; state.armedItemId.clear(); } break; case UIInputEventType::PointerLeave: if (state.dragging) { callbacks.ResetDropTarget(state); } break; case UIInputEventType::FocusLost: { const bool requestPointerRelease = state.dragging; ResetInteractionSession(state, callbacks); state.pressPosition = {}; state.requestPointerCapture = false; state.requestPointerRelease = requestPointerRelease; } break; default: break; } } return result; } } // namespace XCEngine::UI::Editor::Collections::DragDropInteraction