关键节点
This commit is contained in:
751
editor/app/Features/Hierarchy/HierarchyPanel.cpp
Normal file
751
editor/app/Features/Hierarchy/HierarchyPanel.cpp
Normal file
@@ -0,0 +1,751 @@
|
||||
#include "HierarchyPanel.h"
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
#include <XCEditor/Foundation/UIEditorPanelInputFilter.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "State/EditorCommandFocusService.h"
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
|
||||
inline constexpr float kDragThreshold = 4.0f;
|
||||
inline constexpr UIColor kDragPreviewColor(0.92f, 0.92f, 0.92f, 0.42f);
|
||||
|
||||
::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons);
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons) {
|
||||
return icons != nullptr
|
||||
? icons->Resolve(BuiltInIconKind::GameObject)
|
||||
: ::XCEngine::UI::UITextureHandle {};
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
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 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::SetTextMeasurer(
|
||||
const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer) {
|
||||
m_textMeasurer = textMeasurer;
|
||||
}
|
||||
|
||||
void HierarchyPanel::ResetInteractionState() {
|
||||
m_filterHostState = {};
|
||||
m_filterHostFrame = {};
|
||||
m_treeInteractionState = {};
|
||||
m_treeFrame = {};
|
||||
m_dragState = {};
|
||||
ClearRenameState();
|
||||
ResetTransientState();
|
||||
}
|
||||
|
||||
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;
|
||||
RebuildItems();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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 (!TryStartUIEditorTreeViewInlineRenameSession(
|
||||
m_renameState,
|
||||
m_renameFrame,
|
||||
m_pendingRenameItemId,
|
||||
node->label,
|
||||
bounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pendingRenameItemId.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
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 (!HasUIEditorTreeViewValidBounds(bounds)) {
|
||||
ClearRenameState();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateUIEditorTreeViewInlineRenameSession(
|
||||
m_renameState,
|
||||
m_renameFrame,
|
||||
bounds,
|
||||
inputEvents);
|
||||
|
||||
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 {
|
||||
return BuildUIEditorTreeViewInlineRenameBounds(
|
||||
layout,
|
||||
GetPresentedTreeItems(),
|
||||
itemId,
|
||||
ResolveUIEditorTreeViewHostedTextFieldMetrics());
|
||||
}
|
||||
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& HierarchyPanel::GetPresentedTreeItems() const {
|
||||
return ResolveUIEditorFilterableTreeHostItems(m_filterHostFrame);
|
||||
}
|
||||
|
||||
const ::XCEngine::UI::Widgets::UIExpansionModel&
|
||||
HierarchyPanel::GetPresentedExpansionModel() const {
|
||||
return ResolveUIEditorFilterableTreeHostExpansionModel(m_filterHostFrame);
|
||||
}
|
||||
|
||||
bool HierarchyPanel::HasActivePointerCapture() const {
|
||||
return TreeDrag::HasActivePointerCapture(m_dragState) ||
|
||||
HasActiveUIEditorTreeViewPointerCapture(m_treeInteractionState);
|
||||
}
|
||||
|
||||
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,
|
||||
const UIEditorHostedPanelDispatchEntry& dispatchEntry) {
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& presentedItems =
|
||||
GetPresentedTreeItems();
|
||||
const std::vector<UIInputEvent> filteredEvents = BuildUIEditorHostedTreeViewInputEvents(
|
||||
bounds,
|
||||
inputEvents,
|
||||
UIEditorHostedTreeViewInputOptions{
|
||||
.allowInteraction = dispatchEntry.allowInteraction,
|
||||
.hasInputFocus =
|
||||
dispatchEntry.focused &&
|
||||
!IsUIEditorFilterableTreeHostSearchFocused(m_filterHostState),
|
||||
.captureActive = HasActivePointerCapture()
|
||||
},
|
||||
dispatchEntry.focusGained,
|
||||
dispatchEntry.focusLost);
|
||||
|
||||
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 CanInsertBeforeItem(
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) const {
|
||||
if (draggedItemId.empty() ||
|
||||
targetItemId.empty() ||
|
||||
draggedItemId == targetItemId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<std::string> targetParentId =
|
||||
model.GetParentId(targetItemId);
|
||||
return targetParentId.has_value()
|
||||
? model.CanReparent(draggedItemId, targetParentId.value())
|
||||
: model.ContainsNode(draggedItemId);
|
||||
}
|
||||
|
||||
bool CanInsertAfterItem(
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) const {
|
||||
return CanInsertBeforeItem(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 CommitInsertBeforeItem(
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) {
|
||||
return sceneRuntime != nullptr &&
|
||||
sceneRuntime->MoveGameObjectBefore(draggedItemId, targetItemId);
|
||||
}
|
||||
|
||||
bool CommitInsertAfterItem(
|
||||
std::string_view draggedItemId,
|
||||
std::string_view targetItemId) {
|
||||
return sceneRuntime != nullptr &&
|
||||
sceneRuntime->MoveGameObjectAfter(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,
|
||||
presentedItems,
|
||||
filteredEvents,
|
||||
bounds,
|
||||
callbacks,
|
||||
kDragThreshold);
|
||||
if (dragResult.selectionForced) {
|
||||
SyncSceneRuntimeSelectionFromTree();
|
||||
EmitSelectionEvent();
|
||||
}
|
||||
if (dragResult.dropCommitted) {
|
||||
SyncModelFromScene();
|
||||
m_filterHostFrame = UpdateUIEditorFilterableTreeHost(
|
||||
m_filterHostState,
|
||||
m_filterHostFrame.layout.bounds,
|
||||
"Hierarchy.TreeFilter",
|
||||
{},
|
||||
m_treeItems,
|
||||
m_expansion);
|
||||
m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout(
|
||||
bounds,
|
||||
GetPresentedTreeItems(),
|
||||
GetPresentedExpansionModel(),
|
||||
ResolveUIEditorTreeViewMetrics(),
|
||||
m_treeInteractionState.verticalOffset);
|
||||
EmitReparentEvent(
|
||||
dragResult.droppedToRoot ? EventKind::MovedToRoot : EventKind::Reparented,
|
||||
dragResult.draggedItemId,
|
||||
dragResult.dropTargetItemId);
|
||||
}
|
||||
}
|
||||
|
||||
void HierarchyPanel::Update(
|
||||
const UIEditorHostedPanelDispatchEntry& dispatchEntry,
|
||||
const std::vector<UIInputEvent>& inputEvents) {
|
||||
ResetTransientState();
|
||||
|
||||
if (!dispatchEntry.mounted) {
|
||||
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> panelFilteredEvents = BuildUIEditorPanelInputEvents(
|
||||
dispatchEntry.bounds,
|
||||
inputEvents,
|
||||
UIEditorPanelInputFilterOptions{
|
||||
.allowPointerInBounds = dispatchEntry.allowInteraction,
|
||||
.allowPointerWhileCaptured = HasActivePointerCapture(),
|
||||
.allowKeyboardInput = dispatchEntry.focused,
|
||||
.allowFocusEvents =
|
||||
dispatchEntry.focused ||
|
||||
HasActivePointerCapture() ||
|
||||
dispatchEntry.focusGained ||
|
||||
dispatchEntry.focusLost,
|
||||
.includePointerLeave =
|
||||
dispatchEntry.allowInteraction || HasActivePointerCapture()
|
||||
},
|
||||
dispatchEntry.focusGained,
|
||||
dispatchEntry.focusLost);
|
||||
m_filterHostFrame = UpdateUIEditorFilterableTreeHost(
|
||||
m_filterHostState,
|
||||
dispatchEntry.bounds,
|
||||
"Hierarchy.TreeFilter",
|
||||
panelFilteredEvents,
|
||||
m_treeItems,
|
||||
m_expansion);
|
||||
if (m_filterHostFrame.result.queryChanged) {
|
||||
m_treeInteractionState.verticalOffset = 0.0f;
|
||||
}
|
||||
if (IsUIEditorFilterableTreeHostSearchFocused(m_filterHostState)) {
|
||||
m_treeInteractionState.treeViewState.focused = false;
|
||||
}
|
||||
SyncTreeFocusState(panelFilteredEvents);
|
||||
TryClaimHostedPanelCommandFocus(
|
||||
m_commandFocusService,
|
||||
EditorActionRoute::Hierarchy,
|
||||
panelFilteredEvents,
|
||||
dispatchEntry.bounds,
|
||||
dispatchEntry.allowInteraction);
|
||||
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& presentedItems =
|
||||
GetPresentedTreeItems();
|
||||
const Widgets::UIEditorTreeViewMetrics treeMetrics =
|
||||
ResolveUIEditorTreeViewMetrics();
|
||||
::XCEngine::UI::Widgets::UIExpansionModel* activeExpansionModel =
|
||||
m_filterHostFrame.result.filteringActive
|
||||
? &m_filterHostFrame.filteredExpansionModel
|
||||
: &m_expansion;
|
||||
const Widgets::UIEditorTreeViewLayout layout =
|
||||
Widgets::BuildUIEditorTreeViewLayout(
|
||||
m_filterHostFrame.layout.treeRect,
|
||||
presentedItems,
|
||||
*activeExpansionModel,
|
||||
treeMetrics,
|
||||
m_treeInteractionState.verticalOffset);
|
||||
|
||||
if (m_renameState.active || !m_pendingRenameItemId.empty()) {
|
||||
m_treeFrame.layout = layout;
|
||||
m_treeFrame.result = {};
|
||||
TryStartQueuedRenameSession(layout);
|
||||
UpdateRenameSession(panelFilteredEvents, layout);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<UIInputEvent> treeEvents = BuildUIEditorHostedTreeViewInputEvents(
|
||||
m_filterHostFrame.layout.treeRect,
|
||||
inputEvents,
|
||||
UIEditorHostedTreeViewInputOptions{
|
||||
.allowInteraction = dispatchEntry.allowInteraction,
|
||||
.hasInputFocus =
|
||||
dispatchEntry.focused &&
|
||||
!IsUIEditorFilterableTreeHostSearchFocused(m_filterHostState),
|
||||
.captureActive = HasActivePointerCapture()
|
||||
},
|
||||
dispatchEntry.focusGained,
|
||||
dispatchEntry.focusLost);
|
||||
const std::vector<UIInputEvent> interactionEvents =
|
||||
TreeDrag::BuildInteractionInputEvents(
|
||||
m_dragState,
|
||||
layout,
|
||||
presentedItems,
|
||||
treeEvents,
|
||||
kDragThreshold);
|
||||
m_treeFrame = UpdateUIEditorTreeViewInteraction(
|
||||
m_treeInteractionState,
|
||||
m_treeSelection,
|
||||
*activeExpansionModel,
|
||||
m_filterHostFrame.layout.treeRect,
|
||||
presentedItems,
|
||||
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,
|
||||
m_filterHostFrame.layout.treeRect,
|
||||
dispatchEntry);
|
||||
}
|
||||
|
||||
void HierarchyPanel::Append(UIDrawList& drawList) const {
|
||||
if (!m_visible ||
|
||||
m_filterHostFrame.layout.bounds.width <= 0.0f ||
|
||||
m_filterHostFrame.layout.bounds.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& presentedItems =
|
||||
GetPresentedTreeItems();
|
||||
const Widgets::UIEditorTreeViewPalette palette = ResolveUIEditorTreeViewPalette();
|
||||
const Widgets::UIEditorTreeViewMetrics metrics = ResolveUIEditorTreeViewMetrics();
|
||||
drawList.AddFilledRect(m_filterHostFrame.layout.bounds, palette.surfaceColor);
|
||||
AppendUIEditorFilterableTreeHostSearchField(
|
||||
drawList,
|
||||
m_filterHostFrame,
|
||||
m_filterHostState,
|
||||
"Search",
|
||||
{},
|
||||
{},
|
||||
m_textMeasurer);
|
||||
if (m_treeFrame.layout.bounds.width <= 0.0f ||
|
||||
m_treeFrame.layout.bounds.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
AppendUIEditorTreeViewBackground(
|
||||
drawList,
|
||||
m_treeFrame.layout,
|
||||
presentedItems,
|
||||
m_treeSelection,
|
||||
m_treeInteractionState.treeViewState,
|
||||
palette,
|
||||
metrics);
|
||||
AppendUIEditorTreeViewForeground(
|
||||
drawList,
|
||||
m_treeFrame.layout,
|
||||
presentedItems,
|
||||
palette,
|
||||
metrics);
|
||||
|
||||
if (m_renameState.active) {
|
||||
AppendUIEditorInlineRenameSession(
|
||||
drawList,
|
||||
m_renameFrame,
|
||||
m_renameState,
|
||||
ResolveUIEditorTreeViewInlineRenamePalette(),
|
||||
BuildUIEditorTreeViewInlineRenameMetrics(
|
||||
BuildRenameBounds(m_renameState.itemId, m_treeFrame.layout)),
|
||||
m_textMeasurer);
|
||||
}
|
||||
|
||||
AppendUIEditorTreeViewDropPreview(
|
||||
drawList,
|
||||
m_treeFrame.layout,
|
||||
presentedItems,
|
||||
m_dragState.dragging && m_dragState.validDropTarget,
|
||||
m_dragState.dropPlacement,
|
||||
m_dragState.dropTargetItemId,
|
||||
kDragPreviewColor,
|
||||
1.0f,
|
||||
0.0f);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
Reference in New Issue
Block a user