From 1a7a350880e505b7e75355420dd25c677abc6e91 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 26 Jan 2022 08:12:34 -0800 Subject: [PATCH] Introduce a new Focus Mode API function to retrieve full list of EntityIds that are in focus. (#7159) Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../FocusMode/FocusModeInterface.h | 5 ++ .../FocusMode/FocusModeSystemComponent.cpp | 62 +++++++++++++++++++ .../FocusMode/FocusModeSystemComponent.h | 15 +++++ .../Tests/FocusMode/EditorFocusModeTests.cpp | 53 ++++++++++++++++ 4 files changed, 135 insertions(+) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h index 5455e8d773..74d207af64 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -17,6 +17,8 @@ namespace AzToolsFramework { + using EntityIdList = AZStd::vector; + //! FocusModeInterface //! Interface to handle the Editor Focus Mode. class FocusModeInterface @@ -36,6 +38,9 @@ namespace AzToolsFramework //! @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(AzFramework::EntityContextId entityContextId) = 0; + //! Returns a list of the ids of all the entities that are descendants of the focus root. + virtual EntityIdList GetFocusedEntities(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/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp index 16640f4edf..3266daedb3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -40,10 +40,14 @@ namespace AzToolsFramework void FocusModeSystemComponent::Activate() { AZ::Interface::Register(this); + EditorEntityInfoNotificationBus::Handler::BusConnect(); + Prefab::PrefabPublicNotificationBus::Handler::BusConnect(); } void FocusModeSystemComponent::Deactivate() { + Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect(); + EditorEntityInfoNotificationBus::Handler::BusDisconnect(); AZ::Interface::Unregister(this); } @@ -89,6 +93,9 @@ namespace AzToolsFramework AZ::EntityId previousFocusEntityId = m_focusRoot; m_focusRoot = entityId; + + RefreshFocusedEntityIdList(); + FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, previousFocusEntityId, m_focusRoot); } @@ -102,6 +109,11 @@ namespace AzToolsFramework return m_focusRoot; } + EntityIdList FocusModeSystemComponent::GetFocusedEntities([[maybe_unused]] AzFramework::EntityContextId entityContextId) + { + return m_focusedEntityIdList; + } + bool FocusModeSystemComponent::IsInFocusSubTree(AZ::EntityId entityId) const { if (m_focusRoot == AZ::EntityId()) @@ -112,4 +124,54 @@ namespace AzToolsFramework return AzToolsFramework::IsInFocusSubTree(entityId, m_focusRoot); } + void FocusModeSystemComponent::OnEntityInfoUpdatedAddChildEnd(AZ::EntityId parentId, AZ::EntityId childId) + { + // If the parent's entityId is in the list, add the child. + if (auto iter = AZStd::find(m_focusedEntityIdList.begin(), m_focusedEntityIdList.end(), parentId); + iter != m_focusedEntityIdList.end()) + { + m_focusedEntityIdList.push_back(childId); + } + } + + void FocusModeSystemComponent::OnEntityInfoUpdatedRemoveChildEnd([[maybe_unused]] AZ::EntityId parentId, AZ::EntityId childId) + { + // If the removed entityId is in the list, remove it. + if (auto iter = AZStd::find(m_focusedEntityIdList.begin(), m_focusedEntityIdList.end(), childId); + iter != m_focusedEntityIdList.end()) + { + m_focusedEntityIdList.erase(iter); + } + } + + void FocusModeSystemComponent::OnPrefabInstancePropagationEnd() + { + // Can't rely on any of the entities in the list to still exist, refresh the whole thing. + RefreshFocusedEntityIdList(); + } + + void FocusModeSystemComponent::RefreshFocusedEntityIdList() + { + m_focusedEntityIdList.clear(); + + AZStd::queue entityIdQueue; + entityIdQueue.push(m_focusRoot); + + while (!entityIdQueue.empty()) + { + AZ::EntityId entityId = entityIdQueue.front(); + entityIdQueue.pop(); + + m_focusedEntityIdList.push_back(entityId); + + EntityIdList children; + EditorEntityInfoRequestBus::EventResult(children, entityId, &EditorEntityInfoRequestBus::Events::GetChildren); + + for (AZ::EntityId childEntityId : children) + { + entityIdQueue.push(childEntityId); + } + } + } + } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h index dabaa6aaf4..eb246a387f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.h @@ -11,7 +11,9 @@ #include #include +#include #include +#include namespace AzToolsFramework { @@ -21,6 +23,8 @@ namespace AzToolsFramework class FocusModeSystemComponent final : public AZ::Component , private FocusModeInterface + , private EditorEntityInfoNotificationBus::Handler + , private Prefab::PrefabPublicNotificationBus::Handler { public: AZ_COMPONENT(FocusModeSystemComponent, "{6CE522FE-2057-4794-BD05-61E04BD8EA30}"); @@ -42,10 +46,21 @@ namespace AzToolsFramework void SetFocusRoot(AZ::EntityId entityId) override; void ClearFocusRoot(AzFramework::EntityContextId entityContextId) override; AZ::EntityId GetFocusRoot(AzFramework::EntityContextId entityContextId) override; + EntityIdList GetFocusedEntities(AzFramework::EntityContextId entityContextId) override; bool IsInFocusSubTree(AZ::EntityId entityId) const override; + // EditorEntityInfoNotificationBus overrides ... + void OnEntityInfoUpdatedAddChildEnd(AZ::EntityId parentId, AZ::EntityId childId) override; + void OnEntityInfoUpdatedRemoveChildEnd(AZ::EntityId parentId, AZ::EntityId childId) override; + + // PrefabPublicNotificationBus overrides ... + void OnPrefabInstancePropagationEnd() override; + private: + void RefreshFocusedEntityIdList(); + AZ::EntityId m_focusRoot; + EntityIdList m_focusedEntityIdList; }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp index bac60230d6..524323230b 100644 --- a/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/FocusMode/EditorFocusModeTests.cpp @@ -7,6 +7,7 @@ */ #include +#include namespace UnitTest { @@ -30,6 +31,58 @@ namespace UnitTest EXPECT_EQ(m_focusModeInterface->GetFocusRoot(m_editorEntityContextId), AZ::EntityId()); } + TEST_F(EditorFocusModeFixture, GetFocusedEntitiesBase) + { + m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]); + + AzToolsFramework::EntityIdList entities = m_focusModeInterface->GetFocusedEntities(m_editorEntityContextId); + + EXPECT_EQ(entities.size(), 5); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[StreetEntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[CarEntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[Passenger1EntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[SportsCarEntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[Passenger2EntityName]) != entities.end()); + } + + TEST_F(EditorFocusModeFixture, GetFocusedEntitiesSiblings) + { + m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]); + + AzToolsFramework::EntityIdList entities = m_focusModeInterface->GetFocusedEntities(m_editorEntityContextId); + + EXPECT_EQ(entities.size(), 2); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[SportsCarEntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[Passenger2EntityName]) != entities.end()); + } + + TEST_F(EditorFocusModeFixture, GetFocusedEntitiesAddEntity) + { + m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]); + + AZ::EntityId testEntityId = CreateEditorEntity("Test", m_entityMap[Passenger2EntityName]); + + AzToolsFramework::EntityIdList entities = m_focusModeInterface->GetFocusedEntities(m_editorEntityContextId); + + EXPECT_EQ(entities.size(), 3); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[SportsCarEntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[Passenger2EntityName]) != entities.end()); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), testEntityId) != entities.end()); + } + + TEST_F(EditorFocusModeFixture, GetFocusedEntitiesRemoveEntity) + { + m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]); + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequests::DeleteEntityAndAllDescendants, m_entityMap[Passenger2EntityName]); + + AzToolsFramework::EntityIdList entities = m_focusModeInterface->GetFocusedEntities(m_editorEntityContextId); + + EXPECT_EQ(entities.size(), 1); + EXPECT_TRUE(AZStd::find(entities.begin(), entities.end(), m_entityMap[SportsCarEntityName]) != entities.end()); + } + TEST_F(EditorFocusModeFixture, IsInFocusSubTreeAncestorsDescendants) { // When the focus is set to an entity, all its descendants are in the focus subtree while the ancestors aren't.