new_editor: support between-row hierarchy drag drop
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -18,9 +20,12 @@ using Widgets::HitTestUIEditorTreeView;
|
||||
using Widgets::UIEditorTreeViewHitTarget;
|
||||
using Widgets::UIEditorTreeViewHitTargetKind;
|
||||
using Widgets::UIEditorTreeViewInvalidIndex;
|
||||
using ::XCEngine::UI::Editor::UIEditorTreeViewDropPreviewPlacement;
|
||||
|
||||
struct State : DragDropInteraction::State {
|
||||
bool dropToRoot = false;
|
||||
UIEditorTreeViewDropPreviewPlacement dropPlacement =
|
||||
UIEditorTreeViewDropPreviewPlacement::None;
|
||||
};
|
||||
|
||||
struct ProcessResult : DragDropInteraction::ProcessResult {
|
||||
@@ -42,6 +47,77 @@ inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
template <typename Callbacks>
|
||||
inline constexpr bool SupportsSiblingInsertion() {
|
||||
return requires(
|
||||
Callbacks& callbacks,
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) {
|
||||
{ callbacks.CanInsertBeforeItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
|
||||
{ callbacks.CanInsertAfterItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
|
||||
{ callbacks.CommitInsertBeforeItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
|
||||
{ callbacks.CommitInsertAfterItem(draggedItemId, targetItemId) } -> std::convertible_to<bool>;
|
||||
};
|
||||
}
|
||||
|
||||
inline UIEditorTreeViewDropPreviewPlacement ResolveRowDropPlacement(
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const UIEditorTreeViewHitTarget& hitTarget,
|
||||
const UIPoint& point) {
|
||||
if (hitTarget.visibleIndex >= layout.rowRects.size()) {
|
||||
return UIEditorTreeViewDropPreviewPlacement::None;
|
||||
}
|
||||
|
||||
const UIRect& rowRect = layout.rowRects[hitTarget.visibleIndex];
|
||||
const float edgeBand =
|
||||
(std::min)((std::max)(rowRect.height * 0.25f, 4.0f), 8.0f);
|
||||
if (point.y <= rowRect.y + edgeBand) {
|
||||
return UIEditorTreeViewDropPreviewPlacement::BeforeItem;
|
||||
}
|
||||
if (point.y >= rowRect.y + rowRect.height - edgeBand) {
|
||||
return UIEditorTreeViewDropPreviewPlacement::AfterItem;
|
||||
}
|
||||
|
||||
return UIEditorTreeViewDropPreviewPlacement::OnItem;
|
||||
}
|
||||
|
||||
inline const Widgets::UIEditorTreeViewItem* ResolveGapInsertionItem(
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const UIPoint& point,
|
||||
UIEditorTreeViewDropPreviewPlacement& outPlacement) {
|
||||
outPlacement = UIEditorTreeViewDropPreviewPlacement::None;
|
||||
if (layout.rowRects.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (point.y < layout.rowRects.front().y) {
|
||||
outPlacement = UIEditorTreeViewDropPreviewPlacement::BeforeItem;
|
||||
const std::size_t itemIndex = layout.visibleItemIndices.front();
|
||||
return itemIndex < items.size() ? &items[itemIndex] : nullptr;
|
||||
}
|
||||
|
||||
for (std::size_t visibleIndex = 0u; visibleIndex + 1u < layout.rowRects.size(); ++visibleIndex) {
|
||||
const UIRect& currentRow = layout.rowRects[visibleIndex];
|
||||
const UIRect& nextRow = layout.rowRects[visibleIndex + 1u];
|
||||
if (point.y > currentRow.y + currentRow.height &&
|
||||
point.y < nextRow.y) {
|
||||
outPlacement = UIEditorTreeViewDropPreviewPlacement::BeforeItem;
|
||||
const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex + 1u];
|
||||
return itemIndex < items.size() ? &items[itemIndex] : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const UIRect& lastRow = layout.rowRects.back();
|
||||
if (point.y > lastRow.y + lastRow.height) {
|
||||
outPlacement = UIEditorTreeViewDropPreviewPlacement::AfterItem;
|
||||
const std::size_t itemIndex = layout.visibleItemIndices.back();
|
||||
return itemIndex < items.size() ? &items[itemIndex] : nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline const Widgets::UIEditorTreeViewItem* ResolveHitItem(
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
@@ -177,6 +253,7 @@ ProcessResult ProcessInputEvents(
|
||||
void ResetDropTarget(State& state) const {
|
||||
state.dropTargetItemId.clear();
|
||||
state.dropToRoot = false;
|
||||
state.dropPlacement = UIEditorTreeViewDropPreviewPlacement::None;
|
||||
state.validDropTarget = false;
|
||||
}
|
||||
|
||||
@@ -190,14 +267,71 @@ ProcessResult ProcessInputEvents(
|
||||
if (hitItem != nullptr &&
|
||||
(hitTarget.kind == UIEditorTreeViewHitTargetKind::Row ||
|
||||
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) {
|
||||
UIEditorTreeViewDropPreviewPlacement placement =
|
||||
UIEditorTreeViewDropPreviewPlacement::OnItem;
|
||||
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
|
||||
placement =
|
||||
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure
|
||||
? UIEditorTreeViewDropPreviewPlacement::OnItem
|
||||
: ResolveRowDropPlacement(layout, hitTarget, point);
|
||||
}
|
||||
|
||||
state.dropTargetItemId = hitItem->itemId;
|
||||
state.validDropTarget =
|
||||
callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId);
|
||||
state.dropPlacement = placement;
|
||||
switch (placement) {
|
||||
case UIEditorTreeViewDropPreviewPlacement::BeforeItem:
|
||||
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
|
||||
state.validDropTarget =
|
||||
callbacks.CanInsertBeforeItem(
|
||||
draggedItemId,
|
||||
state.dropTargetItemId);
|
||||
}
|
||||
break;
|
||||
|
||||
case UIEditorTreeViewDropPreviewPlacement::AfterItem:
|
||||
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
|
||||
state.validDropTarget =
|
||||
callbacks.CanInsertAfterItem(
|
||||
draggedItemId,
|
||||
state.dropTargetItemId);
|
||||
}
|
||||
break;
|
||||
|
||||
case UIEditorTreeViewDropPreviewPlacement::OnItem:
|
||||
state.validDropTarget =
|
||||
callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
|
||||
UIEditorTreeViewDropPreviewPlacement gapPlacement =
|
||||
UIEditorTreeViewDropPreviewPlacement::None;
|
||||
const Widgets::UIEditorTreeViewItem* gapItem =
|
||||
ResolveGapInsertionItem(layout, items, point, gapPlacement);
|
||||
if (gapItem != nullptr &&
|
||||
gapPlacement != UIEditorTreeViewDropPreviewPlacement::None) {
|
||||
state.dropTargetItemId = gapItem->itemId;
|
||||
state.dropPlacement = gapPlacement;
|
||||
state.validDropTarget =
|
||||
gapPlacement == UIEditorTreeViewDropPreviewPlacement::BeforeItem
|
||||
? callbacks.CanInsertBeforeItem(
|
||||
draggedItemId,
|
||||
state.dropTargetItemId)
|
||||
: callbacks.CanInsertAfterItem(
|
||||
draggedItemId,
|
||||
state.dropTargetItemId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ContainsPoint(bounds, point)) {
|
||||
state.dropToRoot = true;
|
||||
state.dropPlacement = UIEditorTreeViewDropPreviewPlacement::Root;
|
||||
state.validDropTarget = callbacks.CanDropToRoot(draggedItemId);
|
||||
}
|
||||
}
|
||||
@@ -212,11 +346,35 @@ ProcessResult ProcessInputEvents(
|
||||
|
||||
bool CommitDrop(State& state, DragDropInteraction::ProcessResult&) {
|
||||
droppedToRoot = state.dropToRoot;
|
||||
return state.dropToRoot
|
||||
? callbacks.CommitDropToRoot(state.draggedItemId)
|
||||
: callbacks.CommitDropOnItem(
|
||||
switch (state.dropPlacement) {
|
||||
case UIEditorTreeViewDropPreviewPlacement::BeforeItem:
|
||||
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
|
||||
return callbacks.CommitInsertBeforeItem(
|
||||
state.draggedItemId,
|
||||
state.dropTargetItemId);
|
||||
}
|
||||
return false;
|
||||
|
||||
case UIEditorTreeViewDropPreviewPlacement::AfterItem:
|
||||
if constexpr (SupportsSiblingInsertion<Callbacks>()) {
|
||||
return callbacks.CommitInsertAfterItem(
|
||||
state.draggedItemId,
|
||||
state.dropTargetItemId);
|
||||
}
|
||||
return false;
|
||||
|
||||
case UIEditorTreeViewDropPreviewPlacement::Root:
|
||||
return callbacks.CommitDropToRoot(state.draggedItemId);
|
||||
|
||||
case UIEditorTreeViewDropPreviewPlacement::OnItem:
|
||||
return callbacks.CommitDropOnItem(
|
||||
state.draggedItemId,
|
||||
state.dropTargetItemId);
|
||||
|
||||
case UIEditorTreeViewDropPreviewPlacement::None:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} adaptedCallbacks{ layout, items, bounds, callbacks };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user