Refactor new editor scene runtime ownership
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
#include "HierarchyPanelInternal.h"
|
||||
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
using namespace HierarchyPanelInternal;
|
||||
namespace TreeDrag = TreeItemDragDrop;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -26,11 +32,23 @@ UIEditorHostCommandDispatchResult BuildDispatchResult(
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HasValidBounds(const UIRect& bounds) {
|
||||
return bounds.width > 0.0f && bounds.height > 0.0f;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void HierarchyPanel::Initialize() {
|
||||
m_model = HierarchyModel::BuildDefault();
|
||||
RebuildItems();
|
||||
SyncModelFromScene();
|
||||
}
|
||||
|
||||
void HierarchyPanel::SetSceneRuntime(EditorSceneRuntime* sceneRuntime) {
|
||||
if (m_sceneRuntime == sceneRuntime) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sceneRuntime = sceneRuntime;
|
||||
SyncModelFromScene();
|
||||
}
|
||||
|
||||
void HierarchyPanel::SetBuiltInIcons(const BuiltInIcons* icons) {
|
||||
@@ -42,6 +60,7 @@ void HierarchyPanel::ResetInteractionState() {
|
||||
m_treeInteractionState = {};
|
||||
m_treeFrame = {};
|
||||
m_dragState = {};
|
||||
ClearRenameState();
|
||||
ResetTransientState();
|
||||
}
|
||||
|
||||
@@ -58,39 +77,89 @@ const UIEditorPanelContentHostPanelState* HierarchyPanel::FindMountedHierarchyPa
|
||||
|
||||
void HierarchyPanel::ResetTransientState() {
|
||||
m_frameEvents.clear();
|
||||
m_dragState.requestPointerCapture = false;
|
||||
m_dragState.requestPointerRelease = false;
|
||||
TreeDrag::ResetTransientRequests(m_dragState);
|
||||
}
|
||||
|
||||
void HierarchyPanel::SyncModelFromScene() {
|
||||
if (m_sceneRuntime != nullptr) {
|
||||
m_sceneRuntime->RefreshScene();
|
||||
}
|
||||
|
||||
const HierarchyModel sceneModel =
|
||||
HierarchyModel::BuildFromScene(
|
||||
m_sceneRuntime != nullptr
|
||||
? m_sceneRuntime->GetActiveScene()
|
||||
: nullptr);
|
||||
if (!m_model.HasSameTree(sceneModel) || m_treeItems.empty()) {
|
||||
m_model = sceneModel;
|
||||
if (m_sceneRuntime != nullptr && !m_model.Empty()) {
|
||||
m_sceneRuntime->EnsureSceneSelection();
|
||||
}
|
||||
RebuildItems();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_sceneRuntime != nullptr && !m_model.Empty()) {
|
||||
m_sceneRuntime->EnsureSceneSelection();
|
||||
}
|
||||
|
||||
SyncTreeSelectionFromSceneRuntime();
|
||||
}
|
||||
|
||||
void HierarchyPanel::RebuildItems() {
|
||||
const auto icon = ResolveGameObjectIcon(m_icons);
|
||||
const std::string previousSelection =
|
||||
m_selection.HasSelection() ? m_selection.GetSelectedId() : std::string();
|
||||
|
||||
m_treeItems = m_model.BuildTreeItems(icon);
|
||||
m_expansion.Expand("player");
|
||||
m_expansion.Expand("environment");
|
||||
m_expansion.Expand("props");
|
||||
SyncTreeSelectionFromSceneRuntime();
|
||||
}
|
||||
|
||||
if (!previousSelection.empty() && m_model.ContainsNode(previousSelection)) {
|
||||
m_selection.SetSelection(previousSelection);
|
||||
void HierarchyPanel::SyncTreeSelectionFromSceneRuntime() {
|
||||
if (m_sceneRuntime == nullptr) {
|
||||
m_treeSelection.ClearSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_treeItems.empty()) {
|
||||
m_selection.SetSelection(m_treeItems.front().itemId);
|
||||
const std::string selectedItemId = m_sceneRuntime->GetSelectedItemId();
|
||||
if (!selectedItemId.empty() && m_model.ContainsNode(selectedItemId)) {
|
||||
m_treeSelection.SetSelection(selectedItemId);
|
||||
return;
|
||||
}
|
||||
|
||||
m_selection.ClearSelection();
|
||||
m_treeSelection.ClearSelection();
|
||||
}
|
||||
|
||||
void HierarchyPanel::SyncSceneRuntimeSelectionFromTree() {
|
||||
if (m_sceneRuntime == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_treeSelection.HasSelection()) {
|
||||
if (!m_sceneRuntime->SetSelection(m_treeSelection.GetSelectedId())) {
|
||||
m_sceneRuntime->RefreshScene();
|
||||
}
|
||||
} else {
|
||||
m_sceneRuntime->ClearSelection();
|
||||
}
|
||||
|
||||
if (!m_model.Empty()) {
|
||||
m_sceneRuntime->EnsureSceneSelection();
|
||||
}
|
||||
|
||||
SyncTreeSelectionFromSceneRuntime();
|
||||
}
|
||||
|
||||
const HierarchyNode* HierarchyPanel::GetSelectedNode() const {
|
||||
if (!m_selection.HasSelection()) {
|
||||
if (m_sceneRuntime != nullptr) {
|
||||
const std::string selectedItemId = m_sceneRuntime->GetSelectedItemId();
|
||||
if (!selectedItemId.empty()) {
|
||||
return m_model.FindNode(selectedItemId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_treeSelection.HasSelection()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_model.FindNode(m_selection.GetSelectedId());
|
||||
return m_model.FindNode(m_treeSelection.GetSelectedId());
|
||||
}
|
||||
|
||||
void HierarchyPanel::EmitSelectionEvent() {
|
||||
@@ -127,6 +196,155 @@ void HierarchyPanel::EmitRenameRequestedEvent(std::string_view itemId) {
|
||||
m_frameEvents.push_back(std::move(event));
|
||||
}
|
||||
|
||||
void HierarchyPanel::ClearRenameState() {
|
||||
m_renameState = {};
|
||||
m_renameFrame = {};
|
||||
m_pendingRenameItemId.clear();
|
||||
}
|
||||
|
||||
void HierarchyPanel::QueueRenameSession(std::string_view itemId) {
|
||||
if (itemId.empty() || !m_model.ContainsNode(itemId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_renameState.active && m_renameState.itemId == itemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingRenameItemId = std::string(itemId);
|
||||
}
|
||||
|
||||
bool HierarchyPanel::TryStartQueuedRenameSession(
|
||||
const Widgets::UIEditorTreeViewLayout& layout) {
|
||||
if (m_pendingRenameItemId.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HierarchyNode* node = m_model.FindNode(m_pendingRenameItemId);
|
||||
if (node == nullptr) {
|
||||
m_pendingRenameItemId.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIRect bounds = BuildRenameBounds(m_pendingRenameItemId, layout);
|
||||
if (!HasValidBounds(bounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Widgets::UIEditorTextFieldMetrics textFieldMetrics =
|
||||
BuildUIEditorInlineRenameTextFieldMetrics(
|
||||
bounds,
|
||||
BuildUIEditorPropertyGridTextFieldMetrics(
|
||||
ResolveUIEditorPropertyGridMetrics(),
|
||||
ResolveUIEditorTextFieldMetrics()));
|
||||
|
||||
UIEditorInlineRenameSessionRequest request = {};
|
||||
request.beginSession = true;
|
||||
request.itemId = m_pendingRenameItemId;
|
||||
request.initialText = node->label;
|
||||
request.bounds = bounds;
|
||||
m_renameFrame = UpdateUIEditorInlineRenameSession(
|
||||
m_renameState,
|
||||
request,
|
||||
{},
|
||||
textFieldMetrics);
|
||||
|
||||
if (m_renameFrame.result.sessionStarted) {
|
||||
m_pendingRenameItemId.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void HierarchyPanel::UpdateRenameSession(
|
||||
const std::vector<UIInputEvent>& inputEvents,
|
||||
const Widgets::UIEditorTreeViewLayout& layout) {
|
||||
if (!m_renameState.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_model.ContainsNode(m_renameState.itemId)) {
|
||||
ClearRenameState();
|
||||
return;
|
||||
}
|
||||
|
||||
const UIRect bounds = BuildRenameBounds(m_renameState.itemId, layout);
|
||||
if (!HasValidBounds(bounds)) {
|
||||
ClearRenameState();
|
||||
return;
|
||||
}
|
||||
|
||||
const Widgets::UIEditorTextFieldMetrics textFieldMetrics =
|
||||
BuildUIEditorInlineRenameTextFieldMetrics(
|
||||
bounds,
|
||||
BuildUIEditorPropertyGridTextFieldMetrics(
|
||||
ResolveUIEditorPropertyGridMetrics(),
|
||||
ResolveUIEditorTextFieldMetrics()));
|
||||
|
||||
UIEditorInlineRenameSessionRequest request = {};
|
||||
request.itemId = m_renameState.itemId;
|
||||
request.initialText = m_renameState.textFieldSpec.value;
|
||||
request.bounds = bounds;
|
||||
m_renameFrame = UpdateUIEditorInlineRenameSession(
|
||||
m_renameState,
|
||||
request,
|
||||
inputEvents,
|
||||
textFieldMetrics);
|
||||
|
||||
if (!m_renameFrame.result.sessionCommitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_renameFrame.result.valueChanged &&
|
||||
m_sceneRuntime != nullptr) {
|
||||
m_sceneRuntime->RenameGameObject(
|
||||
m_renameFrame.result.itemId,
|
||||
m_renameFrame.result.valueAfter);
|
||||
}
|
||||
|
||||
SyncModelFromScene();
|
||||
EmitSelectionEvent();
|
||||
}
|
||||
|
||||
void HierarchyPanel::SyncTreeFocusState(
|
||||
const std::vector<UIInputEvent>& inputEvents) {
|
||||
for (const UIInputEvent& event : inputEvents) {
|
||||
if (event.type == ::XCEngine::UI::UIInputEventType::FocusGained) {
|
||||
m_treeInteractionState.treeViewState.focused = true;
|
||||
} else if (event.type == ::XCEngine::UI::UIInputEventType::FocusLost) {
|
||||
m_treeInteractionState.treeViewState.focused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UIRect HierarchyPanel::BuildRenameBounds(
|
||||
std::string_view itemId,
|
||||
const Widgets::UIEditorTreeViewLayout& layout) const {
|
||||
if (itemId.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t visibleIndex =
|
||||
FindVisibleIndexForItemId(layout, m_treeItems, itemId);
|
||||
if (visibleIndex == UIEditorTreeViewInvalidIndex ||
|
||||
visibleIndex >= layout.rowRects.size() ||
|
||||
visibleIndex >= layout.labelRects.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const Widgets::UIEditorTextFieldMetrics hostedMetrics =
|
||||
BuildUIEditorPropertyGridTextFieldMetrics(
|
||||
ResolveUIEditorPropertyGridMetrics(),
|
||||
ResolveUIEditorTextFieldMetrics());
|
||||
const UIRect& rowRect = layout.rowRects[visibleIndex];
|
||||
const UIRect& labelRect = layout.labelRects[visibleIndex];
|
||||
const float x = (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX);
|
||||
const float right = rowRect.x + rowRect.width - 8.0f;
|
||||
const float width = (std::max)(120.0f, right - x);
|
||||
return UIRect(x, rowRect.y, width, rowRect.height);
|
||||
}
|
||||
|
||||
bool HierarchyPanel::WantsHostPointerCapture() const {
|
||||
return m_dragState.requestPointerCapture;
|
||||
}
|
||||
@@ -136,7 +354,7 @@ bool HierarchyPanel::WantsHostPointerRelease() const {
|
||||
}
|
||||
|
||||
bool HierarchyPanel::HasActivePointerCapture() const {
|
||||
return m_dragState.dragging;
|
||||
return TreeDrag::HasActivePointerCapture(m_dragState);
|
||||
}
|
||||
|
||||
const std::vector<HierarchyPanel::Event>& HierarchyPanel::GetFrameEvents() const {
|
||||
@@ -193,20 +411,27 @@ UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand(
|
||||
|
||||
const std::string selectedNodeId = selectedNode->nodeId;
|
||||
const std::string selectedNodeLabel = selectedNode->label;
|
||||
if (m_sceneRuntime == nullptr) {
|
||||
return BuildDispatchResult(false, "Hierarchy scene runtime is unavailable.");
|
||||
}
|
||||
|
||||
if (commandId == "edit.rename") {
|
||||
QueueRenameSession(selectedNodeId);
|
||||
EmitRenameRequestedEvent(selectedNodeId);
|
||||
if (m_visible) {
|
||||
TryStartQueuedRenameSession(m_treeFrame.layout);
|
||||
}
|
||||
return BuildDispatchResult(
|
||||
true,
|
||||
"Hierarchy rename requested for '" + selectedNodeLabel + "'.");
|
||||
}
|
||||
|
||||
if (commandId == "edit.delete") {
|
||||
if (!m_model.DeleteNode(selectedNodeId)) {
|
||||
if (!m_sceneRuntime->DeleteGameObject(selectedNodeId)) {
|
||||
return BuildDispatchResult(false, "Failed to delete the selected hierarchy object.");
|
||||
}
|
||||
|
||||
RebuildItems();
|
||||
SyncModelFromScene();
|
||||
EmitSelectionEvent();
|
||||
return BuildDispatchResult(
|
||||
true,
|
||||
@@ -214,13 +439,13 @@ UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand(
|
||||
}
|
||||
|
||||
if (commandId == "edit.duplicate") {
|
||||
const std::string duplicatedNodeId = m_model.DuplicateNode(selectedNodeId);
|
||||
const std::string duplicatedNodeId =
|
||||
m_sceneRuntime->DuplicateGameObject(selectedNodeId);
|
||||
if (duplicatedNodeId.empty()) {
|
||||
return BuildDispatchResult(false, "Failed to duplicate the selected hierarchy object.");
|
||||
}
|
||||
|
||||
RebuildItems();
|
||||
m_selection.SetSelection(duplicatedNodeId);
|
||||
SyncModelFromScene();
|
||||
EmitSelectionEvent();
|
||||
|
||||
const HierarchyNode* duplicatedNode = m_model.FindNode(duplicatedNodeId);
|
||||
@@ -234,115 +459,6 @@ UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand(
|
||||
return BuildDispatchResult(false, "Hierarchy does not expose this edit command.");
|
||||
}
|
||||
|
||||
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,
|
||||
ResolveUIEditorTreeViewMetrics());
|
||||
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,
|
||||
@@ -356,125 +472,70 @@ void HierarchyPanel::ProcessDragAndFrameEvents(
|
||||
HasActivePointerCapture());
|
||||
|
||||
if (m_treeFrame.result.selectionChanged) {
|
||||
SyncSceneRuntimeSelectionFromTree();
|
||||
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;
|
||||
|
||||
struct HierarchyTreeDragCallbacks {
|
||||
::XCEngine::UI::Widgets::UISelectionModel& selection;
|
||||
HierarchyModel& model;
|
||||
EditorSceneRuntime* sceneRuntime = nullptr;
|
||||
|
||||
bool IsItemSelected(std::string_view itemId) const {
|
||||
return selection.IsSelected(itemId);
|
||||
}
|
||||
m_frameEvents.push_back(std::move(event));
|
||||
|
||||
bool SelectDraggedItem(std::string_view itemId) {
|
||||
return selection.SetSelection(std::string(itemId));
|
||||
}
|
||||
|
||||
bool CanDropOnItem(
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) const {
|
||||
return model.CanReparent(draggedItemId, targetItemId);
|
||||
}
|
||||
|
||||
bool CanDropToRoot(std::string_view draggedItemId) const {
|
||||
return model.GetParentId(draggedItemId).has_value();
|
||||
}
|
||||
|
||||
bool CommitDropOnItem(
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) {
|
||||
return sceneRuntime != nullptr &&
|
||||
sceneRuntime->ReparentGameObject(draggedItemId, targetItemId);
|
||||
}
|
||||
|
||||
bool CommitDropToRoot(std::string_view draggedItemId) {
|
||||
return sceneRuntime != nullptr &&
|
||||
sceneRuntime->MoveGameObjectToRoot(draggedItemId);
|
||||
}
|
||||
} callbacks{ m_treeSelection, m_model, m_sceneRuntime };
|
||||
|
||||
const TreeDrag::ProcessResult dragResult =
|
||||
TreeDrag::ProcessInputEvents(
|
||||
m_dragState,
|
||||
m_treeFrame.layout,
|
||||
m_treeItems,
|
||||
filteredEvents,
|
||||
bounds,
|
||||
callbacks,
|
||||
kDragThreshold);
|
||||
if (dragResult.selectionForced) {
|
||||
SyncSceneRuntimeSelectionFromTree();
|
||||
EmitSelectionEvent();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (dragResult.dropCommitted) {
|
||||
SyncModelFromScene();
|
||||
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
|
||||
bounds,
|
||||
m_treeItems,
|
||||
m_expansion,
|
||||
ResolveUIEditorTreeViewMetrics());
|
||||
EmitReparentEvent(
|
||||
dragResult.droppedToRoot ? EventKind::MovedToRoot : EventKind::Reparented,
|
||||
dragResult.draggedItemId,
|
||||
dragResult.dropTargetItemId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,28 +552,67 @@ void HierarchyPanel::Update(
|
||||
m_visible = false;
|
||||
m_treeFrame = {};
|
||||
m_dragState = {};
|
||||
ClearRenameState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_treeItems.empty()) {
|
||||
RebuildItems();
|
||||
SyncModelFromScene();
|
||||
if (m_renameState.active && !m_model.ContainsNode(m_renameState.itemId)) {
|
||||
ClearRenameState();
|
||||
} else if (!m_pendingRenameItemId.empty() &&
|
||||
!m_model.ContainsNode(m_pendingRenameItemId)) {
|
||||
m_pendingRenameItemId.clear();
|
||||
}
|
||||
|
||||
m_visible = true;
|
||||
const std::vector<UIInputEvent> interactionEvents =
|
||||
BuildInteractionInputEvents(
|
||||
inputEvents,
|
||||
const std::vector<UIInputEvent> filteredEvents = FilterHierarchyInputEvents(
|
||||
panelState->bounds,
|
||||
inputEvents,
|
||||
allowInteraction,
|
||||
panelActive,
|
||||
HasActivePointerCapture());
|
||||
SyncTreeFocusState(filteredEvents);
|
||||
|
||||
const Widgets::UIEditorTreeViewMetrics treeMetrics =
|
||||
ResolveUIEditorTreeViewMetrics();
|
||||
const Widgets::UIEditorTreeViewLayout layout =
|
||||
Widgets::BuildUIEditorTreeViewLayout(
|
||||
panelState->bounds,
|
||||
allowInteraction,
|
||||
panelActive);
|
||||
m_treeItems,
|
||||
m_expansion,
|
||||
treeMetrics);
|
||||
|
||||
if (m_renameState.active || !m_pendingRenameItemId.empty()) {
|
||||
m_treeFrame.layout = layout;
|
||||
m_treeFrame.result = {};
|
||||
TryStartQueuedRenameSession(layout);
|
||||
UpdateRenameSession(filteredEvents, layout);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<UIInputEvent> interactionEvents =
|
||||
TreeDrag::BuildInteractionInputEvents(
|
||||
m_dragState,
|
||||
layout,
|
||||
m_treeItems,
|
||||
filteredEvents,
|
||||
kDragThreshold);
|
||||
m_treeFrame = UpdateUIEditorTreeViewInteraction(
|
||||
m_treeInteractionState,
|
||||
m_selection,
|
||||
m_treeSelection,
|
||||
m_expansion,
|
||||
panelState->bounds,
|
||||
m_treeItems,
|
||||
interactionEvents,
|
||||
ResolveUIEditorTreeViewMetrics());
|
||||
treeMetrics);
|
||||
if (m_treeFrame.result.renameRequested &&
|
||||
!m_treeFrame.result.renameItemId.empty()) {
|
||||
QueueRenameSession(m_treeFrame.result.renameItemId);
|
||||
EmitRenameRequestedEvent(m_treeFrame.result.renameItemId);
|
||||
TryStartQueuedRenameSession(m_treeFrame.layout);
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessDragAndFrameEvents(
|
||||
inputEvents,
|
||||
panelState->bounds,
|
||||
@@ -533,7 +633,7 @@ void HierarchyPanel::Append(UIDrawList& drawList) const {
|
||||
drawList,
|
||||
m_treeFrame.layout,
|
||||
m_treeItems,
|
||||
m_selection,
|
||||
m_treeSelection,
|
||||
m_treeInteractionState.treeViewState,
|
||||
palette,
|
||||
metrics);
|
||||
@@ -544,6 +644,33 @@ void HierarchyPanel::Append(UIDrawList& drawList) const {
|
||||
palette,
|
||||
metrics);
|
||||
|
||||
if (m_renameState.active) {
|
||||
const Widgets::UIEditorTextFieldPalette textFieldPalette =
|
||||
BuildUIEditorPropertyGridTextFieldPalette(
|
||||
ResolveUIEditorPropertyGridPalette(),
|
||||
ResolveUIEditorTextFieldPalette());
|
||||
const Widgets::UIEditorTextFieldMetrics textFieldMetrics =
|
||||
BuildUIEditorInlineRenameTextFieldMetrics(
|
||||
BuildRenameBounds(m_renameState.itemId, m_treeFrame.layout),
|
||||
BuildUIEditorPropertyGridTextFieldMetrics(
|
||||
ResolveUIEditorPropertyGridMetrics(),
|
||||
ResolveUIEditorTextFieldMetrics()));
|
||||
Widgets::AppendUIEditorTextFieldBackground(
|
||||
drawList,
|
||||
m_renameFrame.layout,
|
||||
m_renameState.textFieldSpec,
|
||||
m_renameState.textFieldInteraction.textFieldState,
|
||||
textFieldPalette,
|
||||
textFieldMetrics);
|
||||
Widgets::AppendUIEditorTextFieldForeground(
|
||||
drawList,
|
||||
m_renameFrame.layout,
|
||||
m_renameState.textFieldSpec,
|
||||
m_renameState.textFieldInteraction.textFieldState,
|
||||
textFieldPalette,
|
||||
textFieldMetrics);
|
||||
}
|
||||
|
||||
if (!m_dragState.dragging || !m_dragState.validDropTarget) {
|
||||
return;
|
||||
}
|
||||
@@ -557,7 +684,7 @@ void HierarchyPanel::Append(UIDrawList& drawList) const {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t visibleIndex = FindVisibleIndexForItemId(
|
||||
const std::size_t visibleIndex = TreeDrag::FindVisibleIndexForItemId(
|
||||
m_treeFrame.layout,
|
||||
m_treeItems,
|
||||
m_dragState.dropTargetItemId);
|
||||
|
||||
Reference in New Issue
Block a user