refactor(new_editor): tighten app dependency boundaries
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user