728 lines
22 KiB
C++
728 lines
22 KiB
C++
#include "HierarchyPanelInternal.h"
|
|
|
|
#include "Scene/EditorSceneRuntime.h"
|
|
#include "State/EditorCommandFocusService.h"
|
|
|
|
#include <XCEditor/Collections/UIEditorTreePanelBehavior.h>
|
|
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
|
#include <XCEditor/Fields/UIEditorTextField.h>
|
|
|
|
#include <utility>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
using namespace HierarchyPanelInternal;
|
|
namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop;
|
|
|
|
namespace {
|
|
|
|
UIEditorHostCommandEvaluationResult BuildEvaluationResult(
|
|
bool executable,
|
|
std::string message) {
|
|
UIEditorHostCommandEvaluationResult result = {};
|
|
result.executable = executable;
|
|
result.message = std::move(message);
|
|
return result;
|
|
}
|
|
|
|
UIEditorHostCommandDispatchResult BuildDispatchResult(
|
|
bool commandExecuted,
|
|
std::string message) {
|
|
UIEditorHostCommandDispatchResult result = {};
|
|
result.commandExecuted = commandExecuted;
|
|
result.message = std::move(message);
|
|
return result;
|
|
}
|
|
|
|
bool HasValidBounds(const UIRect& bounds) {
|
|
return bounds.width > 0.0f && bounds.height > 0.0f;
|
|
}
|
|
|
|
bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
|
|
return point.x >= rect.x &&
|
|
point.x <= rect.x + rect.width &&
|
|
point.y >= rect.y &&
|
|
point.y <= rect.y + rect.height;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void HierarchyPanel::Initialize() {
|
|
SyncModelFromScene();
|
|
}
|
|
|
|
void HierarchyPanel::SetSceneRuntime(EditorSceneRuntime* sceneRuntime) {
|
|
if (m_sceneRuntime == sceneRuntime) {
|
|
return;
|
|
}
|
|
|
|
m_sceneRuntime = sceneRuntime;
|
|
SyncModelFromScene();
|
|
}
|
|
|
|
void HierarchyPanel::SetCommandFocusService(
|
|
EditorCommandFocusService* commandFocusService) {
|
|
m_commandFocusService = commandFocusService;
|
|
}
|
|
|
|
void HierarchyPanel::SetBuiltInIcons(const BuiltInIcons* icons) {
|
|
m_icons = icons;
|
|
RebuildItems();
|
|
}
|
|
|
|
void HierarchyPanel::ResetInteractionState() {
|
|
m_treeInteractionState = {};
|
|
m_treeFrame = {};
|
|
m_dragState = {};
|
|
ClearRenameState();
|
|
ResetTransientState();
|
|
}
|
|
|
|
const UIEditorPanelContentHostPanelState* HierarchyPanel::FindMountedHierarchyPanel(
|
|
const UIEditorPanelContentHostFrame& contentHostFrame) const {
|
|
for (const UIEditorPanelContentHostPanelState& panelState : contentHostFrame.panelStates) {
|
|
if (panelState.panelId == kHierarchyPanelId && panelState.mounted) {
|
|
return &panelState;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void HierarchyPanel::ResetTransientState() {
|
|
m_frameEvents.clear();
|
|
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);
|
|
m_treeItems = m_model.BuildTreeItems(icon);
|
|
SyncTreeSelectionFromSceneRuntime();
|
|
}
|
|
|
|
void HierarchyPanel::SyncTreeSelectionFromSceneRuntime() {
|
|
if (m_sceneRuntime == nullptr) {
|
|
m_treeSelection.ClearSelection();
|
|
return;
|
|
}
|
|
|
|
const std::string selectedItemId = m_sceneRuntime->GetSelectedItemId();
|
|
if (!selectedItemId.empty() && m_model.ContainsNode(selectedItemId)) {
|
|
m_treeSelection.SetSelection(selectedItemId);
|
|
return;
|
|
}
|
|
|
|
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_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_treeSelection.GetSelectedId());
|
|
}
|
|
|
|
void HierarchyPanel::EmitSelectionEvent() {
|
|
Event event = {};
|
|
event.kind = EventKind::SelectionChanged;
|
|
if (const HierarchyNode* node = GetSelectedNode(); node != nullptr) {
|
|
event.itemId = node->nodeId;
|
|
event.label = node->label;
|
|
}
|
|
m_frameEvents.push_back(std::move(event));
|
|
}
|
|
|
|
void HierarchyPanel::EmitReparentEvent(
|
|
EventKind kind,
|
|
std::string itemId,
|
|
std::string targetItemId) {
|
|
Event event = {};
|
|
event.kind = kind;
|
|
event.itemId = std::move(itemId);
|
|
event.targetItemId = std::move(targetItemId);
|
|
if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) {
|
|
event.label = node->label;
|
|
}
|
|
m_frameEvents.push_back(std::move(event));
|
|
}
|
|
|
|
void HierarchyPanel::EmitRenameRequestedEvent(std::string_view itemId) {
|
|
Event event = {};
|
|
event.kind = EventKind::RenameRequested;
|
|
event.itemId = std::string(itemId);
|
|
if (const HierarchyNode* node = m_model.FindNode(itemId); node != nullptr) {
|
|
event.label = node->label;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HierarchyPanel::ClaimCommandFocus(
|
|
const std::vector<UIInputEvent>& inputEvents,
|
|
const UIRect& bounds,
|
|
bool allowInteraction) {
|
|
if (m_commandFocusService == nullptr) {
|
|
return;
|
|
}
|
|
|
|
for (const UIInputEvent& event : inputEvents) {
|
|
if (event.type == ::XCEngine::UI::UIInputEventType::FocusGained) {
|
|
m_commandFocusService->ClaimFocus(EditorActionRoute::Hierarchy);
|
|
return;
|
|
}
|
|
|
|
if (!allowInteraction ||
|
|
event.type != ::XCEngine::UI::UIInputEventType::PointerButtonDown ||
|
|
!ContainsPoint(bounds, event.position)) {
|
|
continue;
|
|
}
|
|
|
|
m_commandFocusService->ClaimFocus(EditorActionRoute::Hierarchy);
|
|
return;
|
|
}
|
|
}
|
|
|
|
UIRect HierarchyPanel::BuildRenameBounds(
|
|
std::string_view itemId,
|
|
const Widgets::UIEditorTreeViewLayout& layout) const {
|
|
const Widgets::UIEditorTextFieldMetrics hostedMetrics =
|
|
BuildUIEditorPropertyGridTextFieldMetrics(
|
|
ResolveUIEditorPropertyGridMetrics(),
|
|
ResolveUIEditorTextFieldMetrics());
|
|
return BuildUIEditorTreePanelInlineRenameBounds(
|
|
layout,
|
|
m_treeItems,
|
|
itemId,
|
|
hostedMetrics);
|
|
}
|
|
|
|
bool HierarchyPanel::WantsHostPointerCapture() const {
|
|
return m_dragState.requestPointerCapture;
|
|
}
|
|
|
|
bool HierarchyPanel::WantsHostPointerRelease() const {
|
|
return m_dragState.requestPointerRelease;
|
|
}
|
|
|
|
bool HierarchyPanel::HasActivePointerCapture() const {
|
|
return TreeDrag::HasActivePointerCapture(m_dragState);
|
|
}
|
|
|
|
const std::vector<HierarchyPanel::Event>& HierarchyPanel::GetFrameEvents() const {
|
|
return m_frameEvents;
|
|
}
|
|
|
|
UIEditorHostCommandEvaluationResult HierarchyPanel::EvaluateEditCommand(
|
|
std::string_view commandId) const {
|
|
const HierarchyNode* selectedNode = GetSelectedNode();
|
|
if (selectedNode == nullptr) {
|
|
return BuildEvaluationResult(false, "Select a hierarchy object first.");
|
|
}
|
|
|
|
if (commandId == "edit.rename") {
|
|
return BuildEvaluationResult(
|
|
true,
|
|
"Rename hierarchy object '" + selectedNode->label + "'.");
|
|
}
|
|
|
|
if (commandId == "edit.delete") {
|
|
return BuildEvaluationResult(
|
|
true,
|
|
"Delete hierarchy object '" + selectedNode->label + "'.");
|
|
}
|
|
|
|
if (commandId == "edit.duplicate") {
|
|
return BuildEvaluationResult(
|
|
true,
|
|
"Duplicate hierarchy object '" + selectedNode->label + "'.");
|
|
}
|
|
|
|
if (commandId == "edit.cut" ||
|
|
commandId == "edit.copy" ||
|
|
commandId == "edit.paste") {
|
|
return BuildEvaluationResult(
|
|
false,
|
|
"Hierarchy clipboard has no bound transfer owner in the current shell.");
|
|
}
|
|
|
|
return BuildEvaluationResult(false, "Hierarchy does not expose this edit command.");
|
|
}
|
|
|
|
UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand(
|
|
std::string_view commandId) {
|
|
const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId);
|
|
if (!evaluation.executable) {
|
|
return BuildDispatchResult(false, evaluation.message);
|
|
}
|
|
|
|
const HierarchyNode* selectedNode = GetSelectedNode();
|
|
if (selectedNode == nullptr) {
|
|
return BuildDispatchResult(false, "Select a hierarchy object first.");
|
|
}
|
|
|
|
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_sceneRuntime->DeleteGameObject(selectedNodeId)) {
|
|
return BuildDispatchResult(false, "Failed to delete the selected hierarchy object.");
|
|
}
|
|
|
|
SyncModelFromScene();
|
|
EmitSelectionEvent();
|
|
return BuildDispatchResult(
|
|
true,
|
|
"Deleted hierarchy object '" + selectedNodeLabel + "'.");
|
|
}
|
|
|
|
if (commandId == "edit.duplicate") {
|
|
const std::string duplicatedNodeId =
|
|
m_sceneRuntime->DuplicateGameObject(selectedNodeId);
|
|
if (duplicatedNodeId.empty()) {
|
|
return BuildDispatchResult(false, "Failed to duplicate the selected hierarchy object.");
|
|
}
|
|
|
|
SyncModelFromScene();
|
|
EmitSelectionEvent();
|
|
|
|
const HierarchyNode* duplicatedNode = m_model.FindNode(duplicatedNodeId);
|
|
const std::string duplicatedLabel =
|
|
duplicatedNode != nullptr ? duplicatedNode->label : selectedNodeLabel;
|
|
return BuildDispatchResult(
|
|
true,
|
|
"Duplicated hierarchy object '" + duplicatedLabel + "'.");
|
|
}
|
|
|
|
return BuildDispatchResult(false, "Hierarchy does not expose this edit command.");
|
|
}
|
|
|
|
void HierarchyPanel::ProcessDragAndFrameEvents(
|
|
const std::vector<UIInputEvent>& inputEvents,
|
|
const UIRect& bounds,
|
|
bool allowInteraction,
|
|
bool panelActive) {
|
|
const std::vector<UIInputEvent> filteredEvents = FilterUIEditorTreePanelInputEvents(
|
|
bounds,
|
|
inputEvents,
|
|
UIEditorTreePanelInputFilterOptions{
|
|
.allowInteraction = allowInteraction,
|
|
.panelActive = panelActive,
|
|
.captureActive = HasActivePointerCapture()
|
|
});
|
|
|
|
if (m_treeFrame.result.selectionChanged) {
|
|
SyncSceneRuntimeSelectionFromTree();
|
|
EmitSelectionEvent();
|
|
}
|
|
|
|
struct HierarchyTreeDragCallbacks {
|
|
::XCEngine::UI::Widgets::UISelectionModel& selection;
|
|
HierarchyModel& model;
|
|
EditorSceneRuntime* sceneRuntime = nullptr;
|
|
|
|
bool IsItemSelected(std::string_view itemId) const {
|
|
return selection.IsSelected(itemId);
|
|
}
|
|
|
|
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();
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 = {};
|
|
ClearRenameState();
|
|
return;
|
|
}
|
|
|
|
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> filteredEvents = FilterUIEditorTreePanelInputEvents(
|
|
panelState->bounds,
|
|
inputEvents,
|
|
UIEditorTreePanelInputFilterOptions{
|
|
.allowInteraction = allowInteraction,
|
|
.panelActive = panelActive,
|
|
.captureActive = HasActivePointerCapture()
|
|
});
|
|
SyncTreeFocusState(filteredEvents);
|
|
ClaimCommandFocus(filteredEvents, panelState->bounds, allowInteraction);
|
|
|
|
const Widgets::UIEditorTreeViewMetrics treeMetrics =
|
|
ResolveUIEditorTreeViewMetrics();
|
|
const Widgets::UIEditorTreeViewLayout layout =
|
|
Widgets::BuildUIEditorTreeViewLayout(
|
|
panelState->bounds,
|
|
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 =
|
|
BuildUIEditorTreePanelInteractionInputEvents(
|
|
m_dragState,
|
|
layout,
|
|
m_treeItems,
|
|
filteredEvents,
|
|
false,
|
|
kDragThreshold);
|
|
m_treeFrame = UpdateUIEditorTreeViewInteraction(
|
|
m_treeInteractionState,
|
|
m_treeSelection,
|
|
m_expansion,
|
|
panelState->bounds,
|
|
m_treeItems,
|
|
interactionEvents,
|
|
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,
|
|
allowInteraction,
|
|
panelActive);
|
|
}
|
|
|
|
void HierarchyPanel::Append(UIDrawList& drawList) const {
|
|
if (!m_visible ||
|
|
m_treeFrame.layout.bounds.width <= 0.0f ||
|
|
m_treeFrame.layout.bounds.height <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
const Widgets::UIEditorTreeViewPalette palette = ResolveUIEditorTreeViewPalette();
|
|
const Widgets::UIEditorTreeViewMetrics metrics = ResolveUIEditorTreeViewMetrics();
|
|
AppendUIEditorTreeViewBackground(
|
|
drawList,
|
|
m_treeFrame.layout,
|
|
m_treeItems,
|
|
m_treeSelection,
|
|
m_treeInteractionState.treeViewState,
|
|
palette,
|
|
metrics);
|
|
AppendUIEditorTreeViewForeground(
|
|
drawList,
|
|
m_treeFrame.layout,
|
|
m_treeItems,
|
|
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()));
|
|
AppendUIEditorInlineRenameSession(
|
|
drawList,
|
|
m_renameFrame,
|
|
m_renameState,
|
|
textFieldPalette,
|
|
textFieldMetrics);
|
|
}
|
|
|
|
if (!m_dragState.dragging || !m_dragState.validDropTarget) {
|
|
return;
|
|
}
|
|
|
|
if (m_dragState.dropToRoot) {
|
|
drawList.AddRectOutline(
|
|
m_treeFrame.layout.bounds,
|
|
kDragPreviewColor,
|
|
1.0f,
|
|
0.0f);
|
|
return;
|
|
}
|
|
|
|
const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex(
|
|
m_treeFrame.layout,
|
|
m_treeItems,
|
|
m_dragState.dropTargetItemId);
|
|
if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex ||
|
|
visibleIndex >= m_treeFrame.layout.rowRects.size()) {
|
|
return;
|
|
}
|
|
|
|
drawList.AddRectOutline(
|
|
m_treeFrame.layout.rowRects[visibleIndex],
|
|
kDragPreviewColor,
|
|
1.0f,
|
|
0.0f);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|