diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index b6c2ba00..91daed5e 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -285,7 +285,9 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) set(XCUI_EDITOR_APP_SUPPORT_SOURCES + app/Scene/EditorSceneRuntime.cpp app/Internal/EmbeddedPngLoader.cpp + app/Scene/EditorSceneBridge.cpp ) set(XCUI_EDITOR_APP_PLATFORM_SOURCES diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp index f502f53f..d0ad3078 100644 --- a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -2,6 +2,7 @@ #include "State/EditorContext.h" +#include #include namespace XCEngine::UI::Editor::App::Internal { @@ -121,6 +122,7 @@ void EditorShellRuntime::Update( const Widgets::UIEditorDockHostLayout preUpdateDockLayout = m_shellFrame.workspaceInteractionFrame.dockHostFrame.layout; + m_hierarchyPanel.SetSceneRuntime(&context.GetSceneRuntime()); context.BindEditCommandRoutes(&m_hierarchyPanel, &m_projectPanel); context.SyncSessionFromWorkspace(workspaceController); UIEditorShellInteractionDefinition definition = @@ -175,15 +177,16 @@ void EditorShellRuntime::Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == "hierarchy"); + activePanelId == kHierarchyPanelId); m_projectPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == "project"); + activePanelId == kProjectPanelId); m_traceEntries = SyncWorkspaceEvents(context, *this); m_inspectorPanel.Update( context.GetSession(), + context.GetSceneRuntime(), m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame); m_consolePanel.Update( context.GetSession(), diff --git a/new_editor/app/Composition/WorkspaceEventSync.cpp b/new_editor/app/Composition/WorkspaceEventSync.cpp index 02e47125..a191a583 100644 --- a/new_editor/app/Composition/WorkspaceEventSync.cpp +++ b/new_editor/app/Composition/WorkspaceEventSync.cpp @@ -5,6 +5,8 @@ #include "Features/Project/ProjectPanel.h" #include "Composition/EditorShellRuntime.h" +#include + #include #include @@ -114,15 +116,20 @@ void ApplyHierarchySelection( return; } - if (event.itemId.empty()) { - context.ClearSelection(); + const EditorSceneRuntime& sceneRuntime = context.GetSceneRuntime(); + if (!sceneRuntime.HasSceneSelection()) { + if (context.GetSession().selection.kind == EditorSelectionKind::HierarchyNode) { + context.ClearSelection(); + } return; } EditorSelectionState selection = {}; selection.kind = EditorSelectionKind::HierarchyNode; - selection.itemId = event.itemId; - selection.displayName = event.label.empty() ? event.itemId : event.label; + selection.itemId = sceneRuntime.GetSelectedItemId(); + selection.displayName = sceneRuntime.GetSelectedDisplayName().empty() + ? selection.itemId + : sceneRuntime.GetSelectedDisplayName(); context.SetSelection(std::move(selection)); } @@ -166,14 +173,14 @@ std::vector SyncWorkspaceEvents( ApplyHierarchySelection(context, event); const std::string message = DescribeHierarchyPanelEvent(event); context.SetStatus("Hierarchy", message); - entries.push_back(WorkspaceTraceEntry{ "hierarchy", std::move(message) }); + entries.push_back(WorkspaceTraceEntry{ std::string(kHierarchyPanelId), std::move(message) }); } for (const ProjectPanel::Event& event : runtime.GetProjectPanelEvents()) { ApplyProjectSelection(context, event); const std::string message = DescribeProjectPanelEvent(event); context.SetStatus("Project", message); - entries.push_back(WorkspaceTraceEntry{ "project", std::move(message) }); + entries.push_back(WorkspaceTraceEntry{ std::string(kProjectPanelId), std::move(message) }); } return entries; diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index 533b753c..d32d5235 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -1,10 +1,16 @@ #include "HierarchyPanelInternal.h" +#include "Scene/EditorSceneRuntime.h" + +#include +#include + #include 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& 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& 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::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 HierarchyPanel::BuildInteractionInputEvents( - const std::vector& inputEvents, - const UIRect& bounds, - bool allowInteraction, - bool panelActive) const { - const std::vector 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 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& 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 interactionEvents = - BuildInteractionInputEvents( - inputEvents, + const std::vector 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 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); diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.h b/new_editor/app/Features/Hierarchy/HierarchyPanel.h index d12ea18c..33bd38a1 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.h @@ -1,8 +1,10 @@ #pragma once -#include "Composition/EditorEditCommandRoute.h" +#include "Features/Shared/TreeItemDragDrop.h" #include "HierarchyModel.h" +#include +#include #include #include @@ -17,6 +19,7 @@ namespace XCEngine::UI::Editor::App { class BuiltInIcons; +class EditorSceneRuntime; class HierarchyPanel final : public EditorEditCommandRoute { public: @@ -36,6 +39,7 @@ public: }; void Initialize(); + void SetSceneRuntime(EditorSceneRuntime* sceneRuntime); void SetBuiltInIcons(const BuiltInIcons* icons); void ResetInteractionState(); void Update( @@ -54,22 +58,10 @@ public: std::string_view commandId) override; private: - struct DragState { - std::string armedItemId = {}; - std::string draggedItemId = {}; - std::string dropTargetItemId = {}; - ::XCEngine::UI::UIPoint pressPosition = {}; - bool armed = false; - bool dragging = false; - bool dropToRoot = false; - bool validDropTarget = false; - bool requestPointerCapture = false; - bool requestPointerRelease = false; - }; - const UIEditorPanelContentHostPanelState* FindMountedHierarchyPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const; void ResetTransientState(); + void SyncModelFromScene(); void RebuildItems(); void ProcessDragAndFrameEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, @@ -77,27 +69,40 @@ private: bool allowInteraction, bool panelActive); const HierarchyNode* GetSelectedNode() const; + void SyncTreeSelectionFromSceneRuntime(); + void SyncSceneRuntimeSelectionFromTree(); void EmitSelectionEvent(); void EmitReparentEvent( EventKind kind, std::string itemId, std::string targetItemId); void EmitRenameRequestedEvent(std::string_view itemId); - std::vector<::XCEngine::UI::UIInputEvent> BuildInteractionInputEvents( + void ClearRenameState(); + void QueueRenameSession(std::string_view itemId); + bool TryStartQueuedRenameSession( + const Widgets::UIEditorTreeViewLayout& layout); + void UpdateRenameSession( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const ::XCEngine::UI::UIRect& bounds, - bool allowInteraction, - bool panelActive) const; + const Widgets::UIEditorTreeViewLayout& layout); + void SyncTreeFocusState( + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); + ::XCEngine::UI::UIRect BuildRenameBounds( + std::string_view itemId, + const Widgets::UIEditorTreeViewLayout& layout) const; const BuiltInIcons* m_icons = nullptr; + EditorSceneRuntime* m_sceneRuntime = nullptr; HierarchyModel m_model = {}; std::vector m_treeItems = {}; - ::XCEngine::UI::Widgets::UISelectionModel m_selection = {}; + ::XCEngine::UI::Widgets::UISelectionModel m_treeSelection = {}; ::XCEngine::UI::Widgets::UIExpansionModel m_expansion = {}; UIEditorTreeViewInteractionState m_treeInteractionState = {}; UIEditorTreeViewInteractionFrame m_treeFrame = {}; + UIEditorInlineRenameSessionState m_renameState = {}; + UIEditorInlineRenameSessionFrame m_renameFrame = {}; + std::string m_pendingRenameItemId = {}; std::vector m_frameEvents = {}; - DragState m_dragState = {}; + TreeItemDragDrop::State m_dragState = {}; bool m_visible = false; }; diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index 3ccebce4..1cae469e 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -1,5 +1,8 @@ #include "InspectorPanel.h" +#include "Scene/EditorSceneRuntime.h" + +#include #include #include @@ -13,7 +16,6 @@ using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; -constexpr std::string_view kInspectorPanelId = "inspector"; constexpr float kPanelPadding = 10.0f; constexpr float kHeaderHeight = 22.0f; constexpr float kSectionGap = 10.0f; @@ -55,32 +57,15 @@ const UIEditorPanelContentHostPanelState* InspectorPanel::FindMountedInspectorPa return nullptr; } -void InspectorPanel::BuildPresentation(const EditorSession& session) { +void InspectorPanel::BuildPresentation( + const EditorSession& session, + const EditorSceneRuntime& sceneRuntime) { m_sections.clear(); m_title.clear(); m_subtitle.clear(); m_hasSelection = false; - switch (session.selection.kind) { - case EditorSelectionKind::HierarchyNode: { - m_hasSelection = true; - m_title = session.selection.displayName.empty() - ? std::string("GameObject") - : session.selection.displayName; - m_subtitle = "GameObject"; - - Section identity = {}; - identity.title = "Identity"; - identity.rows = { - { "Type", "GameObject" }, - { "Name", m_title }, - { "Id", session.selection.itemId } - }; - m_sections.push_back(std::move(identity)); - break; - } - - case EditorSelectionKind::ProjectItem: { + if (session.selection.kind == EditorSelectionKind::ProjectItem) { m_hasSelection = true; m_title = session.selection.displayName.empty() ? (session.selection.directory ? std::string("Folder") : std::string("Asset")) @@ -102,19 +87,50 @@ void InspectorPanel::BuildPresentation(const EditorSession& session) { { "Path", PathToUtf8String(session.selection.absolutePath) } }; m_sections.push_back(std::move(location)); - break; + return; } - case EditorSelectionKind::None: - default: - m_title = "Nothing selected"; - m_subtitle = "Select a hierarchy item or project asset."; - break; + if (session.selection.kind == EditorSelectionKind::HierarchyNode && + sceneRuntime.HasSceneSelection()) { + const auto* selectedGameObject = sceneRuntime.GetSelectedGameObject(); + m_hasSelection = true; + m_title = sceneRuntime.GetSelectedDisplayName().empty() + ? std::string("GameObject") + : sceneRuntime.GetSelectedDisplayName(); + m_subtitle = "GameObject"; + + Section identity = {}; + identity.title = "Identity"; + identity.rows = { + { "Type", "GameObject" }, + { "Name", m_title }, + { "Id", sceneRuntime.GetSelectedItemId() } + }; + m_sections.push_back(std::move(identity)); + + if (selectedGameObject != nullptr) { + Section hierarchy = {}; + hierarchy.title = "Hierarchy"; + hierarchy.rows = { + { "Children", std::to_string(selectedGameObject->GetChildCount()) }, + { "Parent", selectedGameObject->GetParent() != nullptr + ? (selectedGameObject->GetParent()->GetName().empty() + ? std::string("GameObject") + : selectedGameObject->GetParent()->GetName()) + : std::string("Scene Root") } + }; + m_sections.push_back(std::move(hierarchy)); + } + return; } + + m_title = "Nothing selected"; + m_subtitle = "Select a hierarchy item or project asset."; } void InspectorPanel::Update( const EditorSession& session, + const EditorSceneRuntime& sceneRuntime, const UIEditorPanelContentHostFrame& contentHostFrame) { const UIEditorPanelContentHostPanelState* panelState = FindMountedInspectorPanel(contentHostFrame); @@ -130,7 +146,7 @@ void InspectorPanel::Update( m_visible = true; m_bounds = panelState->bounds; - BuildPresentation(session); + BuildPresentation(session, sceneRuntime); } void InspectorPanel::Append(UIDrawList& drawList) const { diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index dab6050f..db51fe61 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -1,6 +1,6 @@ #pragma once -#include "State/EditorSession.h" +#include #include @@ -11,10 +11,13 @@ namespace XCEngine::UI::Editor::App { +class EditorSceneRuntime; + class InspectorPanel { public: void Update( const EditorSession& session, + const EditorSceneRuntime& sceneRuntime, const UIEditorPanelContentHostFrame& contentHostFrame); void Append(::XCEngine::UI::UIDrawList& drawList) const; @@ -31,7 +34,9 @@ private: const UIEditorPanelContentHostPanelState* FindMountedInspectorPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const; - void BuildPresentation(const EditorSession& session); + void BuildPresentation( + const EditorSession& session, + const EditorSceneRuntime& sceneRuntime); bool m_visible = false; bool m_hasSelection = false; diff --git a/new_editor/app/Scene/EditorSceneRuntime.cpp b/new_editor/app/Scene/EditorSceneRuntime.cpp new file mode 100644 index 00000000..f99139ab --- /dev/null +++ b/new_editor/app/Scene/EditorSceneRuntime.cpp @@ -0,0 +1,183 @@ +#include "Scene/EditorSceneRuntime.h" + +#include +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +using ::XCEngine::Components::GameObject; +using ::XCEngine::Components::Scene; + +std::string ResolveGameObjectDisplayName(const GameObject& gameObject) { + return gameObject.GetName().empty() + ? std::string("GameObject") + : gameObject.GetName(); +} + +} // namespace + +bool EditorSceneRuntime::Initialize(const std::filesystem::path& projectRoot) { + m_projectRoot = projectRoot; + m_startupSceneResult = EnsureEditorStartupScene(projectRoot); + RefreshScene(); + return m_startupSceneResult.ready; +} + +void EditorSceneRuntime::RefreshScene() { + if (!HasValidSelection()) { + m_selectedGameObjectId.reset(); + } +} + +void EditorSceneRuntime::EnsureSceneSelection() { + if (HasValidSelection()) { + return; + } + + SelectFirstAvailableGameObject(); +} + +const EditorStartupSceneResult& EditorSceneRuntime::GetStartupResult() const { + return m_startupSceneResult; +} + +Scene* EditorSceneRuntime::GetActiveScene() const { + return GetActiveEditorScene(); +} + +bool EditorSceneRuntime::HasSceneSelection() const { + return HasValidSelection(); +} + +std::optional EditorSceneRuntime::GetSelectedGameObjectId() const { + return HasValidSelection() ? m_selectedGameObjectId : std::nullopt; +} + +std::string EditorSceneRuntime::GetSelectedItemId() const { + const std::optional selectedId = GetSelectedGameObjectId(); + return selectedId.has_value() + ? MakeEditorGameObjectItemId(selectedId.value()) + : std::string(); +} + +std::string EditorSceneRuntime::GetSelectedDisplayName() const { + const GameObject* gameObject = GetSelectedGameObject(); + return gameObject != nullptr + ? ResolveGameObjectDisplayName(*gameObject) + : std::string(); +} + +const GameObject* EditorSceneRuntime::GetSelectedGameObject() const { + if (!m_selectedGameObjectId.has_value()) { + return nullptr; + } + + Scene* scene = GetActiveScene(); + return scene != nullptr + ? scene->FindByID(m_selectedGameObjectId.value()) + : nullptr; +} + +bool EditorSceneRuntime::SetSelection(std::string_view itemId) { + const std::optional gameObjectId = + ParseEditorGameObjectItemId(itemId); + if (!gameObjectId.has_value()) { + return false; + } + + return SetSelection(gameObjectId.value()); +} + +bool EditorSceneRuntime::SetSelection(GameObject::ID id) { + if (id == GameObject::INVALID_ID) { + return false; + } + + Scene* scene = GetActiveScene(); + if (scene == nullptr || scene->FindByID(id) == nullptr) { + return false; + } + + const bool changed = + !m_selectedGameObjectId.has_value() || + m_selectedGameObjectId.value() != id; + m_selectedGameObjectId = id; + return changed; +} + +void EditorSceneRuntime::ClearSelection() { + m_selectedGameObjectId.reset(); +} + +GameObject* EditorSceneRuntime::FindGameObject(std::string_view itemId) const { + return FindEditorGameObject(itemId); +} + +bool EditorSceneRuntime::RenameGameObject( + std::string_view itemId, + std::string_view newName) { + const bool renamed = RenameEditorGameObject(itemId, newName); + RefreshScene(); + return renamed; +} + +bool EditorSceneRuntime::DeleteGameObject(std::string_view itemId) { + const bool deleted = DeleteEditorGameObject(itemId); + RefreshScene(); + EnsureSceneSelection(); + return deleted; +} + +std::string EditorSceneRuntime::DuplicateGameObject(std::string_view itemId) { + const std::string duplicatedItemId = DuplicateEditorGameObject(itemId); + if (!duplicatedItemId.empty()) { + SetSelection(duplicatedItemId); + } else { + RefreshScene(); + } + return duplicatedItemId; +} + +bool EditorSceneRuntime::ReparentGameObject( + std::string_view itemId, + std::string_view parentItemId) { + const bool reparented = + ReparentEditorGameObject(itemId, parentItemId); + RefreshScene(); + return reparented; +} + +bool EditorSceneRuntime::MoveGameObjectToRoot(std::string_view itemId) { + const bool moved = MoveEditorGameObjectToRoot(itemId); + RefreshScene(); + return moved; +} + +bool EditorSceneRuntime::HasValidSelection() const { + return GetSelectedGameObject() != nullptr; +} + +bool EditorSceneRuntime::SelectFirstAvailableGameObject() { + Scene* scene = GetActiveScene(); + if (scene == nullptr) { + m_selectedGameObjectId.reset(); + return false; + } + + for (GameObject* root : scene->GetRootGameObjects()) { + if (root == nullptr) { + continue; + } + + m_selectedGameObjectId = root->GetID(); + return true; + } + + m_selectedGameObjectId.reset(); + return false; +} + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/Scene/EditorSceneRuntime.h b/new_editor/app/Scene/EditorSceneRuntime.h new file mode 100644 index 00000000..1325ec0a --- /dev/null +++ b/new_editor/app/Scene/EditorSceneRuntime.h @@ -0,0 +1,59 @@ +#pragma once + +#include "Scene/EditorSceneBridge.h" + +#include +#include +#include + +namespace XCEngine::Components { + +class GameObject; +class Scene; + +} // namespace XCEngine::Components + +namespace XCEngine::UI::Editor::App { + +class EditorSceneRuntime { +public: + bool Initialize(const std::filesystem::path& projectRoot); + + void RefreshScene(); + void EnsureSceneSelection(); + + const EditorStartupSceneResult& GetStartupResult() const; + ::XCEngine::Components::Scene* GetActiveScene() const; + + bool HasSceneSelection() const; + std::optional<::XCEngine::Components::GameObject::ID> GetSelectedGameObjectId() const; + std::string GetSelectedItemId() const; + std::string GetSelectedDisplayName() const; + const ::XCEngine::Components::GameObject* GetSelectedGameObject() const; + + bool SetSelection(std::string_view itemId); + bool SetSelection(::XCEngine::Components::GameObject::ID id); + void ClearSelection(); + + ::XCEngine::Components::GameObject* FindGameObject(std::string_view itemId) const; + bool RenameGameObject( + std::string_view itemId, + std::string_view newName); + bool DeleteGameObject(std::string_view itemId); + std::string DuplicateGameObject(std::string_view itemId); + bool ReparentGameObject( + std::string_view itemId, + std::string_view parentItemId); + bool MoveGameObjectToRoot(std::string_view itemId); + +private: + bool HasValidSelection() const; + bool SelectFirstAvailableGameObject(); + + std::filesystem::path m_projectRoot = {}; + EditorStartupSceneResult m_startupSceneResult = {}; + std::optional<::XCEngine::Components::GameObject::ID> m_selectedGameObjectId = std::nullopt; +}; + +} // namespace XCEngine::UI::Editor::App + diff --git a/new_editor/app/State/EditorContext.cpp b/new_editor/app/State/EditorContext.cpp index 3c9c7286..15c3906c 100644 --- a/new_editor/app/State/EditorContext.cpp +++ b/new_editor/app/State/EditorContext.cpp @@ -1,6 +1,7 @@ #include "EditorContext.h" #include "Composition/EditorShellAssetBuilder.h" +#include "Scene/EditorSceneRuntime.h" #include #include @@ -28,7 +29,7 @@ std::string ComposeStatusText( } // namespace bool EditorContext::Initialize(const std::filesystem::path& repoRoot) { - m_shellAsset = BuildEditorShellAsset(repoRoot); + m_shellAsset = BuildEditorApplicationShellAsset(repoRoot); m_shellValidation = ValidateEditorShellAsset(m_shellAsset); if (!m_shellValidation.IsValid()) { return false; @@ -37,6 +38,8 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) { m_session = {}; m_session.repoRoot = repoRoot; m_session.projectRoot = (repoRoot / "project").lexically_normal(); + m_sceneRuntime = {}; + m_sceneRuntime.Initialize(m_session.projectRoot); m_hostCommandBridge.BindSession(m_session); m_shortcutManager = BuildEditorShellShortcutManager(m_shellAsset); m_shortcutManager.SetHostCommandHandler(&m_hostCommandBridge); @@ -83,6 +86,14 @@ const EditorSession& EditorContext::GetSession() const { return m_session; } +EditorSceneRuntime& EditorContext::GetSceneRuntime() { + return m_sceneRuntime; +} + +const EditorSceneRuntime& EditorContext::GetSceneRuntime() const { + return m_sceneRuntime; +} + void EditorContext::SetSelection(EditorSelectionState selection) { m_session.selection = std::move(selection); } @@ -106,7 +117,7 @@ UIEditorShellInteractionDefinition EditorContext::BuildShellDefinition( const UIEditorWorkspaceController& workspaceController, std::string_view captureText, EditorShellVariant variant) const { - return BuildEditorShellInteractionDefinition( + return BuildEditorApplicationShellInteractionDefinition( m_shellAsset, workspaceController, ComposeStatusText(m_lastStatus, m_lastMessage), diff --git a/new_editor/app/State/EditorContext.h b/new_editor/app/State/EditorContext.h index 9f62195a..be8a46fb 100644 --- a/new_editor/app/State/EditorContext.h +++ b/new_editor/app/State/EditorContext.h @@ -1,9 +1,10 @@ #pragma once -#include "Composition/EditorHostCommandBridge.h" -#include "State/EditorSession.h" #include "Composition/EditorShellVariant.h" +#include "Scene/EditorSceneRuntime.h" +#include +#include #include #include #include @@ -32,6 +33,8 @@ public: const std::string& GetValidationMessage() const; const EditorShellAsset& GetShellAsset() const; const EditorSession& GetSession() const; + EditorSceneRuntime& GetSceneRuntime(); + const EditorSceneRuntime& GetSceneRuntime() const; void SetSelection(EditorSelectionState selection); void ClearSelection(); @@ -60,6 +63,7 @@ private: UIEditorShortcutManager m_shortcutManager = {}; UIEditorShellInteractionServices m_shellServices = {}; EditorSession m_session = {}; + EditorSceneRuntime m_sceneRuntime = {}; EditorHostCommandBridge m_hostCommandBridge = {}; std::string m_lastStatus = {}; std::string m_lastMessage = {}; diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index 061020af..6035b826 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -68,11 +68,6 @@ set(EDITOR_UI_UNIT_TEST_SOURCES add_executable(editor_ui_tests ${EDITOR_UI_UNIT_TEST_SOURCES}) -target_sources(editor_ui_tests - PRIVATE - ${CMAKE_SOURCE_DIR}/new_editor/app/Composition/EditorHostCommandBridge.cpp -) - target_link_libraries(editor_ui_tests PRIVATE XCUIEditorLib @@ -105,3 +100,52 @@ include(GoogleTest) gtest_discover_tests(editor_ui_tests DISCOVERY_MODE PRE_TEST ) + +if(TARGET XCUIEditorAppLib) + add_executable(editor_app_feature_tests + test_project_browser_model.cpp + test_hierarchy_scene_binding.cpp + ) + + target_link_libraries(editor_app_feature_tests + PRIVATE + XCUIEditorAppLib + XCUIEditorLib + XCUIEditorHost + GTest::gtest_main + ) + + target_include_directories(editor_app_feature_tests + PRIVATE + ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/include + ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT} + ${CMAKE_SOURCE_DIR}/engine/include + ) + + if(MSVC) + target_compile_options(editor_app_feature_tests PRIVATE /utf-8 /FS) + set_target_properties(editor_app_feature_tests PROPERTIES + MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>" + COMPILE_PDB_NAME "editor_app_feature_tests-compile" + COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb" + COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Debug" + COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release" + COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel" + COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo" + ) + set_property(TARGET editor_app_feature_tests PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + endif() + + if(WIN32 AND EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll") + add_custom_command(TARGET editor_app_feature_tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll + $/assimp-vc143-mt.dll + ) + endif() + + gtest_discover_tests(editor_app_feature_tests + DISCOVERY_MODE PRE_TEST + ) +endif()