Files
XCEngine/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp

294 lines
10 KiB
C++
Raw Normal View History

#include "HierarchyPanelSupport.h"
namespace XCEngine::UI::Editor::App {
using namespace HierarchyPanelSupport;
std::vector<UIInputEvent> HierarchyPanel::BuildInteractionInputEvents(
const std::vector<UIInputEvent>& inputEvents,
const UIRect& bounds,
bool allowInteraction,
bool panelActive) const {
const std::vector<UIInputEvent> rawEvents = FilterHierarchyInputEvents(
bounds,
inputEvents,
allowInteraction,
panelActive,
HasActivePointerCapture());
struct DragPreviewState {
std::string armedItemId = {};
UIPoint pressPosition = {};
bool armed = false;
bool dragging = false;
};
const Widgets::UIEditorTreeViewLayout layout =
m_treeFrame.layout.bounds.width > 0.0f
? m_treeFrame.layout
: Widgets::BuildUIEditorTreeViewLayout(
bounds,
m_treeItems,
m_expansion,
BuildEditorTreeViewMetrics());
DragPreviewState preview = {};
preview.armed = m_dragState.armed;
preview.armedItemId = m_dragState.armedItemId;
preview.pressPosition = m_dragState.pressPosition;
preview.dragging = m_dragState.dragging;
std::vector<UIInputEvent> filteredEvents = {};
filteredEvents.reserve(rawEvents.size());
for (const UIInputEvent& event : rawEvents) {
bool suppress = false;
switch (event.type) {
case UIInputEventType::PointerButtonDown:
if (event.pointerButton == UIPointerButton::Left) {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(layout, m_treeItems, event.position, &hitTarget);
if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
preview.armed = true;
preview.armedItemId = hitItem->itemId;
preview.pressPosition = event.position;
} else {
preview.armed = false;
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) >=
kDragThreshold * kDragThreshold) {
preview.dragging = true;
suppress = true;
}
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:
break;
}
if (!suppress) {
filteredEvents.push_back(event);
}
}
return filteredEvents;
}
void HierarchyPanel::ProcessDragAndFrameEvents(
const std::vector<UIInputEvent>& inputEvents,
const UIRect& bounds,
bool allowInteraction,
bool panelActive) {
const std::vector<UIInputEvent> filteredEvents = FilterHierarchyInputEvents(
bounds,
inputEvents,
allowInteraction,
panelActive,
HasActivePointerCapture());
if (m_treeFrame.result.selectionChanged) {
EmitSelectionEvent();
}
if (m_treeFrame.result.renameRequested &&
!m_treeFrame.result.renameItemId.empty()) {
Event event = {};
event.kind = EventKind::RenameRequested;
event.itemId = m_treeFrame.result.renameItemId;
if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) {
event.label = node->label;
}
m_frameEvents.push_back(std::move(event));
}
for (const UIInputEvent& event : filteredEvents) {
switch (event.type) {
case UIInputEventType::PointerButtonDown:
if (event.pointerButton == UIPointerButton::Left) {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget);
if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) {
m_dragState.armed = true;
m_dragState.armedItemId = hitItem->itemId;
m_dragState.pressPosition = event.position;
} else {
m_dragState.armed = false;
m_dragState.armedItemId.clear();
}
}
break;
case UIInputEventType::PointerMove:
if (m_dragState.armed &&
!m_dragState.dragging &&
ComputeSquaredDistance(event.position, m_dragState.pressPosition) >=
kDragThreshold * kDragThreshold) {
m_dragState.dragging = !m_dragState.armedItemId.empty();
m_dragState.draggedItemId = m_dragState.armedItemId;
m_dragState.dropTargetItemId.clear();
m_dragState.dropToRoot = false;
m_dragState.validDropTarget = false;
if (m_dragState.dragging) {
m_dragState.requestPointerCapture = true;
if (!m_selection.IsSelected(m_dragState.draggedItemId)) {
m_selection.SetSelection(m_dragState.draggedItemId);
EmitSelectionEvent();
}
}
}
if (m_dragState.dragging) {
UIEditorTreeViewHitTarget hitTarget = {};
const Widgets::UIEditorTreeViewItem* hitItem =
ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget);
m_dragState.dropTargetItemId.clear();
m_dragState.dropToRoot = false;
m_dragState.validDropTarget = false;
if (hitItem != nullptr &&
(hitTarget.kind == UIEditorTreeViewHitTargetKind::Row ||
hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) {
m_dragState.dropTargetItemId = hitItem->itemId;
m_dragState.validDropTarget =
m_model.CanReparent(m_dragState.draggedItemId, m_dragState.dropTargetItemId);
} else if (ContainsPoint(bounds, event.position)) {
m_dragState.dropToRoot = true;
m_dragState.validDropTarget =
m_model.GetParentId(m_dragState.draggedItemId).has_value();
}
}
break;
case UIInputEventType::PointerButtonUp:
if (event.pointerButton != UIPointerButton::Left) {
break;
}
if (m_dragState.dragging) {
if (m_dragState.validDropTarget) {
const std::string draggedItemId = m_dragState.draggedItemId;
const std::string dropTargetItemId = m_dragState.dropTargetItemId;
const bool changed =
m_dragState.dropToRoot
? m_model.MoveToRoot(draggedItemId)
: m_model.Reparent(draggedItemId, dropTargetItemId);
if (changed) {
RebuildItems();
EmitReparentEvent(
m_dragState.dropToRoot ? EventKind::MovedToRoot : EventKind::Reparented,
draggedItemId,
dropTargetItemId);
}
}
m_dragState.armed = false;
m_dragState.dragging = false;
m_dragState.armedItemId.clear();
m_dragState.draggedItemId.clear();
m_dragState.dropTargetItemId.clear();
m_dragState.dropToRoot = false;
m_dragState.validDropTarget = false;
m_dragState.requestPointerRelease = true;
} else {
m_dragState.armed = false;
m_dragState.armedItemId.clear();
}
break;
case UIInputEventType::FocusLost:
if (m_dragState.dragging) {
m_dragState.requestPointerRelease = true;
}
m_dragState = {};
break;
default:
break;
}
}
}
void HierarchyPanel::Update(
const UIEditorPanelContentHostFrame& contentHostFrame,
const std::vector<UIInputEvent>& inputEvents,
bool allowInteraction,
bool panelActive) {
ResetTransientState();
const UIEditorPanelContentHostPanelState* panelState =
FindMountedHierarchyPanel(contentHostFrame);
if (panelState == nullptr) {
m_visible = false;
m_treeFrame = {};
m_dragState = {};
return;
}
if (m_treeItems.empty()) {
RebuildItems();
}
m_visible = true;
const std::vector<UIInputEvent> interactionEvents =
BuildInteractionInputEvents(
inputEvents,
panelState->bounds,
allowInteraction,
panelActive);
m_treeFrame = UpdateUIEditorTreeViewInteraction(
m_treeInteractionState,
m_selection,
m_expansion,
panelState->bounds,
m_treeItems,
interactionEvents,
BuildEditorTreeViewMetrics());
ProcessDragAndFrameEvents(
inputEvents,
panelState->bounds,
allowInteraction,
panelActive);
}
} // namespace XCEngine::UI::Editor::App