diff --git a/Code/Editor/ViewportTitleDlg.cpp b/Code/Editor/ViewportTitleDlg.cpp index 0d05e0bf44..75d16e9a40 100644 --- a/Code/Editor/ViewportTitleDlg.cpp +++ b/Code/Editor/ViewportTitleDlg.cpp @@ -15,6 +15,7 @@ #include "ViewportTitleDlg.h" // Qt +#include #include #include @@ -39,6 +40,7 @@ #include #include #include +#include #include #include @@ -146,6 +148,11 @@ CViewportTitleDlg::~CViewportTitleDlg() AZ::VR::VREventBus::Handler::BusDisconnect(); GetISystem()->GetISystemEventDispatcher()->RemoveListener(this); GetIEditor()->UnregisterNotifyListener(this); + + if (m_prefabViewportFocusPathHandler) + { + delete m_prefabViewportFocusPathHandler; + } } void CViewportTitleDlg::SetupCameraDropdownMenu() @@ -293,8 +300,6 @@ void CViewportTitleDlg::SetViewPane(CLayoutViewPane* pViewPane) ////////////////////////////////////////////////////////////////////////// void CViewportTitleDlg::OnInitDialog() { - m_ui->m_titleBtn->setText(m_title); - // Add a child parented to us that listens for r_displayInfo changes. auto displayInfoHelper = new CViewportTitleDlgDisplayInfoHelper(this); connect(displayInfoHelper, &CViewportTitleDlgDisplayInfoHelper::ViewportInfoStatusUpdated, this, &CViewportTitleDlg::UpdateDisplayInfo); @@ -315,13 +320,28 @@ void CViewportTitleDlg::OnInitDialog() m_cameraSpeed->setFixedWidth(width); + bool isPrefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + + if (isPrefabSystemEnabled) + { + m_prefabViewportFocusPathHandler = new AzToolsFramework::Prefab::PrefabViewportFocusPathHandler(); + m_prefabViewportFocusPathHandler->Initialize(m_ui->m_prefabFocusPath, m_ui->m_prefabFocusBackButton); + } + else + { + m_ui->m_prefabFocusPath->setEnabled(false); + m_ui->m_prefabFocusBackButton->setEnabled(false); + m_ui->m_prefabFocusPath->hide(); + m_ui->m_prefabFocusBackButton->hide(); + } + } ////////////////////////////////////////////////////////////////////////// void CViewportTitleDlg::SetTitle(const QString& title) { m_title = title; - m_ui->m_titleBtn->setText(m_title); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/ViewportTitleDlg.h b/Code/Editor/ViewportTitleDlg.h index 4a2a454907..6996fe7750 100644 --- a/Code/Editor/ViewportTitleDlg.h +++ b/Code/Editor/ViewportTitleDlg.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -176,6 +177,8 @@ protected: QWidgetAction* m_gridSizeActionWidget = nullptr; QWidgetAction* m_angleSizeActionWidget = nullptr; + AzToolsFramework::Prefab::PrefabViewportFocusPathHandler* m_prefabViewportFocusPathHandler = nullptr; + QScopedPointer m_ui; }; diff --git a/Code/Editor/ViewportTitleDlg.ui b/Code/Editor/ViewportTitleDlg.ui index f0679dd2a1..7ba6361cf9 100644 --- a/Code/Editor/ViewportTitleDlg.ui +++ b/Code/Editor/ViewportTitleDlg.ui @@ -28,7 +28,7 @@ 29 - + 10 @@ -42,102 +42,112 @@ 0 - + + + Up one level + + + + :/Breadcrumb/img/UI20/Breadcrumb/arrow_left-default.svg:/Breadcrumb/img/UI20/Breadcrumb/arrow_left-default.svg + + + + + 0 0 - - Qt::NoContextMenu + + + + + + Qt::Horizontal - - Static + + + 40 + 20 + - - 11 + + + + + + Camera settings + + + + :/Menu/camera.svg:/Menu/camera.svg - - - - :/Menu/camera.svg:/Menu/camera.svg - - - - Camera settings - - - - - - - Debug information - - - - :/Menu/debug.svg:/Menu/debug.svg - - - - true - - - - - - - Toggle viewport helpers - - - - :/Menu/helpers.svg:/Menu/helpers.svg - - - - true - - - - - - - Viewport resolution - - - - :/Menu/resolution.svg:/Menu/resolution.svg - - - + + + Debug information + + + + :/Menu/debug.svg:/Menu/debug.svg + + + true + + - - - Other settings - - - - :/Menu/menu.svg:/Menu/menu.svg - - - + + + Toggle viewport helpers + + + + :/Menu/helpers.svg:/Menu/helpers.svg + + + true + + + + + + + Viewport resolution + + + + :/Menu/resolution.svg:/Menu/resolution.svg + + + + + + + Other settings + + + + :/Menu/menu.svg:/Menu/menu.svg + + - AzQtComponents::ButtonDivider + AzQtComponents::BreadCrumbs QWidget -
AzQtComponents/Components/ButtonDivider.h
+
AzQtComponents/Components/Widgets/BreadCrumbs.h
1
- - - - + + + + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h index a2b23fa8d6..75e3bab60f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -12,6 +12,8 @@ #include #include +#include + namespace AzToolsFramework { //! FocusModeInterface @@ -27,11 +29,11 @@ namespace AzToolsFramework virtual void SetFocusRoot(AZ::EntityId entityId) = 0; //! Clears the Editor focus, allowing the user to select the whole level again. - virtual void ClearFocusRoot() = 0; + virtual void ClearFocusRoot(AzFramework::EntityContextId entityContextId) = 0; //! Returns the entity id of the root of the current Editor focus. //! @return The entity id of the root of the Editor focus, or an invalid entity id if no focus is set. - virtual AZ::EntityId GetFocusRoot() = 0; + virtual AZ::EntityId GetFocusRoot(AzFramework::EntityContextId entityContextId) = 0; //! Returns whether the entity id provided is part of the focused sub-tree. virtual bool IsInFocusSubTree(AZ::EntityId entityId) const = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeNotificationBus.h new file mode 100644 index 0000000000..ab8629ac85 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeNotificationBus.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +namespace AzToolsFramework +{ + //! Used to notify when the editor focus changes. + class FocusModeNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AzFramework::EntityContextId; + ////////////////////////////////////////////////////////////////////////// + + //! Triggered when the editor focus is changed to a different entity. + //! @param entityId The entity the focus has been moved to. + virtual void OnEditorFocusChanged(AZ::EntityId entityId) = 0; + + protected: + ~FocusModeNotifications() = default; + }; + + using FocusModeNotificationBus = AZ::EBus; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp index e1c04fcb93..d1c4d37821 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -9,6 +9,7 @@ #include #include +#include #include namespace AzToolsFramework @@ -64,17 +65,23 @@ namespace AzToolsFramework void FocusModeSystemComponent::SetFocusRoot(AZ::EntityId entityId) { + if (m_focusRoot == entityId) + { + return; + } + m_focusRoot = entityId; + FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, m_focusRoot); // TODO - If m_focusRoot != AZ::EntityId(), activate focus mode via ViewportEditorModeTrackerInterface; else, deactivate focus mode } - void FocusModeSystemComponent::ClearFocusRoot() + void FocusModeSystemComponent::ClearFocusRoot([[maybe_unused]] AzFramework::EntityContextId entityContextId) { SetFocusRoot(AZ::EntityId()); } - AZ::EntityId FocusModeSystemComponent::GetFocusRoot() + AZ::EntityId FocusModeSystemComponent::GetFocusRoot([[maybe_unused]] AzFramework::EntityContextId entityContextId) { return m_focusRoot; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h index 27ddebe1ed..dabaa6aaf4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h @@ -40,8 +40,8 @@ namespace AzToolsFramework // FocusModeInterface overrides ... void SetFocusRoot(AZ::EntityId entityId) override; - void ClearFocusRoot() override; - AZ::EntityId GetFocusRoot() override; + void ClearFocusRoot(AzFramework::EntityContextId entityContextId) override; + AZ::EntityId GetFocusRoot(AzFramework::EntityContextId entityContextId) override; bool IsInFocusSubTree(AZ::EntityId entityId) const override; private: diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index d69d0004cc..4e58bbdf05 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -8,9 +8,11 @@ #include +#include #include #include #include +#include namespace AzToolsFramework::Prefab { @@ -23,12 +25,14 @@ namespace AzToolsFramework::Prefab "Instance Entity Mapper Interface could not be found. " "Check that it is being correctly initialized."); + EditorEntityContextNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); } PrefabFocusHandler::~PrefabFocusHandler() { AZ::Interface::Unregister(this); + EditorEntityContextNotificationBus::Handler::BusDisconnect(); } PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) @@ -53,35 +57,76 @@ namespace AzToolsFramework::Prefab focusedInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); } - if (!focusedInstance.has_value()) + return FocusOnPrefabInstance(focusedInstance); + } + + PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) + { + if (index < 0 || index >= m_instanceFocusVector.size()) { - return AZ::Failure(AZStd::string( - "Prefab Focus Handler: Couldn't find owning instance of entityId provided.")); + return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); } - m_focusedInstance = focusedInstance; - m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + InstanceOptionalReference focusedInstance = m_instanceFocusVector[index]; - FocusModeInterface* focusModeInterface = AZ::Interface::Get(); - if (focusModeInterface) + return FocusOnPrefabInstance(focusedInstance); + } + + PrefabFocusOperationResult PrefabFocusHandler::FocusOnPrefabInstance(InstanceOptionalReference focusedInstance) + { + if (!focusedInstance.has_value()) { - focusModeInterface->SetFocusRoot(focusedInstance->get().GetContainerEntityId()); + return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); + } + + if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get()) + { + m_focusedInstance = focusedInstance; + m_focusedTemplateId = focusedInstance->get().GetTemplateId(); + + AZ::EntityId containerEntityId; + + if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) + { + containerEntityId = focusedInstance->get().GetContainerEntityId(); + + // Select the container entity + AzToolsFramework::SelectEntity(containerEntityId); + } + else + { + containerEntityId = AZ::EntityId(); + + // Clear the selection + AzToolsFramework::SelectEntities({}); + + } + + // Focus on the descendants of the container entity + if (FocusModeInterface* focusModeInterface = AZ::Interface::Get()) + { + focusModeInterface->SetFocusRoot(containerEntityId); + } + + RefreshInstanceFocusList(); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); } return AZ::Success(); } - TemplateId PrefabFocusHandler::GetFocusedPrefabTemplateId() + TemplateId PrefabFocusHandler::GetFocusedPrefabTemplateId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const { return m_focusedTemplateId; } - InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance() + InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance( + [[maybe_unused]] AzFramework::EntityContextId entityContextId) const { return m_focusedInstance; } - bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) + bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const { if (!m_focusedInstance.has_value()) { @@ -99,4 +144,44 @@ namespace AzToolsFramework::Prefab return instance.has_value() && (&instance->get() == &m_focusedInstance->get()); } + const AZ::IO::Path& PrefabFocusHandler::GetPrefabFocusPath([[maybe_unused]] AzFramework::EntityContextId entityContextId) const + { + return m_instanceFocusPath; + } + + const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const + { + return aznumeric_cast(m_instanceFocusVector.size()); + } + + void PrefabFocusHandler::OnEntityStreamLoadSuccess() + { + // Focus on the root prefab (AZ::EntityId() will default to it) + FocusOnOwningPrefab(AZ::EntityId()); + } + + void PrefabFocusHandler::RefreshInstanceFocusList() + { + m_instanceFocusVector.clear(); + m_instanceFocusPath.clear(); + + AZStd::list instanceFocusList; + + // Use a support list to easily push front while traversing the prefab hierarchy + InstanceOptionalReference currentInstance = m_focusedInstance; + while (currentInstance.has_value()) + { + instanceFocusList.push_front(currentInstance); + + currentInstance = currentInstance->get().GetParentInstance(); + } + + // Populate internals using the support list + for (auto& instance : instanceFocusList) + { + m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName()); + m_instanceFocusVector.emplace_back(instance); + } + } + } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index fa7727894d..2ccec36882 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -21,6 +22,7 @@ namespace AzToolsFramework::Prefab //! Handles Prefab Focus mode, determining which prefab file entity changes will target. class PrefabFocusHandler final : private PrefabFocusInterface + , private EditorEntityContextNotificationBus::Handler { public: AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); @@ -28,15 +30,26 @@ namespace AzToolsFramework::Prefab PrefabFocusHandler(); ~PrefabFocusHandler(); - // PrefabFocusInterface override ... + // PrefabFocusInterface overrides ... PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override; - TemplateId GetFocusedPrefabTemplateId() override; - InstanceOptionalReference GetFocusedPrefabInstance() override; - bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) override; + PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override; + TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override; + InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override; + bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override; + const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const override; + const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; + + // EditorEntityContextNotificationBus overrides ... + void OnEntityStreamLoadSuccess() override; private: + PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); + void RefreshInstanceFocusList(); + InstanceOptionalReference m_focusedInstance; TemplateId m_focusedTemplateId; + AZStd::vector m_instanceFocusVector; + AZ::IO::Path m_instanceFocusPath; InstanceEntityMapperInterface* m_instanceEntityMapperInterface; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h index 833a5ef7c8..1c0f4f85e9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusInterface.h @@ -11,6 +11,8 @@ #include #include +#include + #include #include @@ -28,16 +30,27 @@ namespace AzToolsFramework::Prefab //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0; + //! Set the focused prefab instance to the instance at position index of the current path. + //! @param index The index of the instance in the current path that we want the prefab system to focus on. + virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0; + //! Returns the template id of the instance the prefab system is focusing on. - virtual TemplateId GetFocusedPrefabTemplateId() = 0; + virtual TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const = 0; //! Returns a reference to the instance the prefab system is focusing on. - virtual InstanceOptionalReference GetFocusedPrefabInstance() = 0; + virtual InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const = 0; //! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants. //! @param entityId The entityId of the queried entity. //! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise. - virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) = 0; + virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0; + + //! Returns the path from the root instance to the currently focused instance. + //! @return A path composed from the names of the container entities for the instance path. + virtual const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const = 0; + + //! Returns the size of the path to the currently focused instance. + virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0; }; } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusNotificationBus.h new file mode 100644 index 0000000000..31bab719e2 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusNotificationBus.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework::Prefab +{ + //! Used to notify when the editor focus changes. + class PrefabFocusNotifications + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AzFramework::EntityContextId; + ////////////////////////////////////////////////////////////////////////// + + //! Triggered when the editor focus is changed to a different prefab. + virtual void OnPrefabFocusChanged() = 0; + + protected: + ~PrefabFocusNotifications() = default; + }; + + using PrefabFocusNotificationBus = AZ::EBus; + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index 2d20ac7364..4364ae1efc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -39,11 +39,20 @@ namespace AzToolsFramework AZ_Assert((m_editorEntityFrameworkInterface != nullptr), "EntityOutlinerTreeView requires a EditorEntityFrameworkInterface instance on Construction."); + + AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + + FocusModeNotificationBus::Handler::BusConnect(editorEntityContextId); + viewport()->setMouseTracking(true); } EntityOutlinerTreeView::~EntityOutlinerTreeView() { + FocusModeNotificationBus::Handler::BusDisconnect(); + ClearQueuedMouseEvent(); } @@ -303,6 +312,11 @@ namespace AzToolsFramework StyledTreeView::StartCustomDrag(indexListSorted, supportedActions); } + + void EntityOutlinerTreeView::OnEditorFocusChanged([[maybe_unused]] AZ::EntityId entityId) + { + viewport()->repaint(); + } } #include diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx index 5b0134366b..2ddbaaafa9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx @@ -15,6 +15,7 @@ #include #include +#include #include #endif @@ -35,6 +36,7 @@ namespace AzToolsFramework //! of other entities. If the selection updates instantly, this would never be possible. class EntityOutlinerTreeView : public AzQtComponents::StyledTreeView + , private FocusModeNotificationBus::Handler { Q_OBJECT; public: @@ -61,6 +63,9 @@ namespace AzToolsFramework void dropEvent(QDropEvent* event) override; void leaveEvent(QEvent* event) override; + // FocusModeNotificationBus overrides ... + void OnEditorFocusChanged(AZ::EntityId entityId) override; + //! Renders the left side of the item: appropriate background, branch lines, icons. void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp new file mode 100644 index 0000000000..6b0de5dc53 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include + +namespace AzToolsFramework::Prefab +{ + PrefabViewportFocusPathHandler::PrefabViewportFocusPathHandler() + { + // Get default EntityContextId + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + + // Connect to Prefab Focus Notifications + PrefabFocusNotificationBus::Handler::BusConnect(m_editorEntityContextId); + } + + PrefabViewportFocusPathHandler::~PrefabViewportFocusPathHandler() + { + // Disconnect from Prefab Focus Notifications + PrefabFocusNotificationBus::Handler::BusDisconnect(); + } + + void PrefabViewportFocusPathHandler::Initialize(AzQtComponents::BreadCrumbs* breadcrumbsWidget, QToolButton* backButton) + { + // Get reference to the PrefabFocusInterface handler + m_prefabFocusInterface = AZ::Interface::Get(); + if (m_prefabFocusInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabViewportFocusPathHandler construction."); + return; + } + + // Initialize Widgets + m_breadcrumbsWidget = breadcrumbsWidget; + m_backButton = backButton; + + // If a part of the path is clicked, focus on that instance + connect(m_breadcrumbsWidget, &AzQtComponents::BreadCrumbs::linkClicked, this, + [&](const QString&, int linkIndex) + { + m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, linkIndex); + } + ); + + // The back button will allow user to go one level up + connect(m_backButton, &QToolButton::clicked, this, + [&]() + { + if (int length = m_prefabFocusInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1) + { + m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2); + } + } + ); + } + + void PrefabViewportFocusPathHandler::OnPrefabFocusChanged() + { + // Push new Path + m_breadcrumbsWidget->pushPath(m_prefabFocusInterface->GetPrefabFocusPath(m_editorEntityContextId).c_str()); + } + +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h new file mode 100644 index 0000000000..ce7744fb1b --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace AzToolsFramework::Prefab +{ + class PrefabFocusInterface; + + class PrefabViewportFocusPathHandler + : public PrefabFocusNotificationBus::Handler + , private QObject + { + public: + PrefabViewportFocusPathHandler(); + ~PrefabViewportFocusPathHandler(); + + void Initialize(AzQtComponents::BreadCrumbs* breadcrumbsWidget, QToolButton* backButton); + + // PrefabFocusNotificationBus overrides ... + void OnPrefabFocusChanged() override; + + private: + AzQtComponents::BreadCrumbs* m_breadcrumbsWidget = nullptr; + QToolButton* m_backButton = nullptr; + + AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + + PrefabFocusInterface* m_prefabFocusInterface = nullptr; + }; +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 209eadf59e..f09d4f9b72 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -151,6 +151,7 @@ set(FILES Fingerprinting/TypeFingerprinter.h Fingerprinting/TypeFingerprinter.cpp FocusMode/FocusModeInterface.h + FocusMode/FocusModeNotificationBus.h FocusMode/FocusModeSystemComponent.h FocusMode/FocusModeSystemComponent.cpp Logger/TraceLogger.cpp @@ -636,6 +637,7 @@ set(FILES Prefab/PrefabFocusHandler.h Prefab/PrefabFocusHandler.cpp Prefab/PrefabFocusInterface.h + Prefab/PrefabFocusNotificationBus.h Prefab/PrefabIdTypes.h Prefab/PrefabLoader.h Prefab/PrefabLoader.cpp @@ -734,6 +736,8 @@ set(FILES UI/Prefab/PrefabIntegrationInterface.h UI/Prefab/PrefabUiHandler.h UI/Prefab/PrefabUiHandler.cpp + UI/Prefab/PrefabViewportFocusPathHandler.h + UI/Prefab/PrefabViewportFocusPathHandler.cpp PythonTerminal/ScriptHelpDialog.cpp PythonTerminal/ScriptHelpDialog.h PythonTerminal/ScriptHelpDialog.ui diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp index bc026a4baf..c309261a27 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.cpp @@ -27,6 +27,9 @@ namespace AzToolsFramework // register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus GetApplication()->RegisterComponentDescriptor(UnitTest::BoundsTestComponent::CreateDescriptor()); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + GenerateTestHierarchy(); } diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h index a461b45e1b..f038408012 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeFixture.h @@ -32,6 +32,8 @@ namespace AzToolsFramework FocusModeInterface* m_focusModeInterface = nullptr; public: + AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + AzFramework::CameraState m_cameraState; inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f); diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp index 750137814d..968bb3f293 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeSelectionTests.cpp @@ -58,7 +58,7 @@ namespace AzToolsFramework TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnLevel) { // Clear the focus, disabling focus mode - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); // Clear selection ClearSelectedEntities(); @@ -85,6 +85,9 @@ namespace AzToolsFramework auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 1); EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnItself) @@ -101,6 +104,9 @@ namespace AzToolsFramework auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 1); EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnSibling) @@ -113,9 +119,12 @@ namespace AzToolsFramework // Click on Car Entity ClickAtWorldPositionOnViewport(CarEntityPosition); - // entity is selected + // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 0); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnDescendant) @@ -128,8 +137,11 @@ namespace AzToolsFramework // Click on Car Entity ClickAtWorldPositionOnViewport(CarEntityPosition); - // entity is selected + // Verify entity is selected auto selectedEntitiesAfter = GetSelectedEntities(); EXPECT_EQ(selectedEntitiesAfter.size(), 0); + + // Clear the focus, disabling focus mode + m_focusModeInterface->ClearFocusRoot(AzFramework::EntityContextId::CreateNull()); } } diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp index 7dfdff655a..22bc7a494c 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp @@ -14,10 +14,10 @@ namespace AzToolsFramework { // When an entity is set as the focus root, GetFocusRoot should return its EntityId. m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]); - EXPECT_EQ(m_focusModeInterface->GetFocusRoot(), m_entityMap[CarEntityName]); + EXPECT_EQ(m_focusModeInterface->GetFocusRoot(m_editorEntityContextId), m_entityMap[CarEntityName]); // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_ClearFocus) @@ -26,8 +26,8 @@ namespace AzToolsFramework m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]); // Calling ClearFocusRoot restores the default focus root (which is an invalid EntityId). - m_focusModeInterface->ClearFocusRoot(); - EXPECT_EQ(m_focusModeInterface->GetFocusRoot(), AZ::EntityId()); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); + EXPECT_EQ(m_focusModeInterface->GetFocusRoot(m_editorEntityContextId), AZ::EntityId()); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_AncestorsDescendants) @@ -45,7 +45,7 @@ namespace AzToolsFramework } // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Siblings) @@ -63,7 +63,7 @@ namespace AzToolsFramework } // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Leaf) @@ -81,7 +81,7 @@ namespace AzToolsFramework } // Restore default expected focus. - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); } TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Clear) @@ -91,7 +91,7 @@ namespace AzToolsFramework // When the focus is cleared, the whole level is in the focus subtree; so we expect all entities to return true. { - m_focusModeInterface->ClearFocusRoot(); + m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId); EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[CityEntityName]), true); EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[StreetEntityName]), true); diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp index d3c3a2250b..14eee84b7d 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabFocus/PrefabFocusTests.cpp @@ -72,6 +72,9 @@ namespace UnitTest m_prefabFocusInterface = AZ::Interface::Get(); ASSERT_TRUE(m_prefabFocusInterface != nullptr); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + GenerateTestHierarchy(); } @@ -88,6 +91,7 @@ namespace UnitTest AZStd::unique_ptr m_rootInstance; PrefabFocusInterface* m_prefabFocusInterface = nullptr; + AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); inline static const char* CityEntityName = "City"; inline static const char* StreetEntityName = "Street"; @@ -102,9 +106,11 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing the container entity of the root prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CityEntityName]->GetContainerEntityId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CityEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), + m_instanceMap[CityEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CityEntityName]); } @@ -115,9 +121,11 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing a nested entity of the root prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_entityMap[CityEntityName]->GetId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CityEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), + m_instanceMap[CityEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CityEntityName]); } @@ -128,9 +136,10 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing the container entity of a nested prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CarEntityName]->GetContainerEntityId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CarEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), m_instanceMap[CarEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CarEntityName]); } @@ -141,9 +150,10 @@ namespace UnitTest // Verify FocusOnOwningPrefab works when passing a nested entity of the a nested prefab. { m_prefabFocusInterface->FocusOnOwningPrefab(m_entityMap[Passenger1EntityName]->GetId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), m_instanceMap[CarEntityName]->GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), m_instanceMap[CarEntityName]->GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), m_instanceMap[CarEntityName]); } @@ -160,9 +170,10 @@ namespace UnitTest EXPECT_TRUE(rootPrefabInstance.has_value()); m_prefabFocusInterface->FocusOnOwningPrefab(AZ::EntityId()); - EXPECT_EQ(m_prefabFocusInterface->GetFocusedPrefabTemplateId(), rootPrefabInstance->get().GetTemplateId()); + EXPECT_EQ( + m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), rootPrefabInstance->get().GetTemplateId()); - auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(); + auto instance = m_prefabFocusInterface->GetFocusedPrefabInstance(m_editorEntityContextId); EXPECT_TRUE(instance.has_value()); EXPECT_EQ(&instance->get(), &rootPrefabInstance->get()); }