Files
XCEngine/new_editor/include/XCEditor/Collections/UIEditorDragDropInteraction.h

243 lines
7.2 KiB
C
Raw Normal View History

#pragma once
#include <XCEngine/UI/Types.h>
#include <string>
#include <vector>
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 <typename StateLike>
inline void ResetTransientRequests(StateLike& state) {
state.requestPointerCapture = false;
state.requestPointerRelease = false;
}
template <typename StateLike>
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 <typename StateLike>
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 <typename ResolveDraggableItem>
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 <typename StateLike, typename Callbacks>
inline void ResetInteractionSession(StateLike& state, Callbacks& callbacks) {
state.armed = false;
state.dragging = false;
state.armedItemId.clear();
state.draggedItemId.clear();
callbacks.ResetDropTarget(state);
}
template <typename StateLike, typename Callbacks>
ProcessResult ProcessInputEvents(
StateLike& state,
const std::vector<UIInputEvent>& 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