From bb8971a3addd11eb803cebb1287f9741c2ece612 Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Tue, 5 Oct 2021 11:48:27 -0700 Subject: [PATCH] LYN-5288 | Clicking on a Prefab in the viewport should select the entire Prefab and not an individual Entity (#4462) * Setup work for the ContainerEntity SystemComponent and Interface. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Introduce Container Entity Notification Bus Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Introduce a proxy model to control open/closed state of entity containers. Register prefab containers as entity containers. Profit. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Add open state to OnContainerEntityStatusChanged notification + improvements to comments. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Fix to notification trigger to include new arguments. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Fix issue where the Level container would not be expanded correctly. The Level container is now no longer a container entity (since we don't need to be able to close it). Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Revert the addition of an extra proxy layer (which was causing issues) and just move the container logic to the existing filter. Fix bug in the dataChanged signal. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Fix column count in dataChanged signal to correctly update all column and fix visual glitches. Limit container registration to the prefab WIP flag so that the changes can be submitted with an opt-in mechanism. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Add doubleclick behavior on Outliner items - enters focus mode when double clicking on prefab containers. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * override sourceModel() to store pointer to avoid dynamic casting at every filterAcceptsRow call. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Minor comment fixes and nits Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Move container selection logic to a helper function in the ContainerEntityInterface to simplify reusing it in the near future. Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> * Support lazy initialization for tests (since we do not load a level, the lazy initialization in OnEntityStreamLoadSuccess does not trigger) Signed-off-by: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> --- .../Application/ToolsApplication.cpp | 2 + .../AzToolsFrameworkModule.cpp | 2 + .../ContainerEntityInterface.h | 61 +++++++++ .../ContainerEntityNotificationBus.h | 41 ++++++ .../ContainerEntitySystemComponent.cpp | 120 ++++++++++++++++++ .../ContainerEntitySystemComponent.h | 54 ++++++++ .../Prefab/PrefabFocusHandler.cpp | 72 ++++++++++- .../Prefab/PrefabFocusHandler.h | 17 ++- .../EditorEntityUiHandlerBase.cpp | 46 +++++-- .../EditorEntityUiHandlerBase.h | 3 + .../UI/Outliner/EntityOutlinerListModel.cpp | 25 +++- .../UI/Outliner/EntityOutlinerListModel.hxx | 5 + .../EntityOutlinerSortFilterProxyModel.cpp | 22 ++++ .../EntityOutlinerSortFilterProxyModel.hxx | 12 +- .../UI/Outliner/EntityOutlinerWidget.cpp | 9 +- .../UI/Outliner/EntityOutlinerWidget.hxx | 2 + .../UI/Prefab/PrefabIntegrationManager.cpp | 32 ++++- .../UI/Prefab/PrefabIntegrationManager.h | 3 + .../UI/Prefab/PrefabUiHandler.cpp | 15 +++ .../UI/Prefab/PrefabUiHandler.h | 3 +- .../ViewportSelection/EditorHelpers.cpp | 11 +- .../aztoolsframework_files.cmake | 4 + 22 files changed, 531 insertions(+), 30 deletions(-) create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp create mode 100644 Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index 717e0c6f8a..c54735fe91 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -251,6 +252,7 @@ namespace AzToolsFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), + azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp index b68d086892..05d67d9d71 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ namespace AzToolsFramework Components::EditorSelectionAccentSystemComponent::CreateDescriptor(), EditorEntityContextComponent::CreateDescriptor(), EditorEntityFixupComponent::CreateDescriptor(), + ContainerEntitySystemComponent::CreateDescriptor(), FocusModeSystemComponent::CreateDescriptor(), SliceMetadataEntityContextComponent::CreateDescriptor(), SliceRequestComponent::CreateDescriptor(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h new file mode 100644 index 0000000000..45da3a9d8f --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h @@ -0,0 +1,61 @@ +/* + * 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 +{ + //! Outcome object that returns an error message in case of failure to allow caller to handle internal errors. + using ContainerEntityOperationResult = AZ::Outcome; + + //! ContainerEntityInterface + //! An entity registered as Container is just like a regular entity when open. If its state is changed + //! to closed, all descendants of the entity will be treated as part of the entity itself. Selecting any + //! descendant will result in the container being selected, and descendants will be hidden until the + //! container is opened. + class ContainerEntityInterface + { + public: + AZ_RTTI(ContainerEntityInterface, "{0A877C3A-726C-4FD2-BAFE-A2B9F1DE78E4}"); + + //! Registers the entity as a container. The container will be closed by default. + //! @param entityId The entityId that will be registered as a container. + virtual ContainerEntityOperationResult RegisterEntityAsContainer(AZ::EntityId entityId) = 0; + + //! Unregisters the entity as a container. + //! The system will retain the closed state in case the entity is registered again later, but + //! if queried the entity will no longer behave as a container. + //! @param entityId The entityId that will be unregistered as a container. + virtual ContainerEntityOperationResult UnregisterEntityAsContainer(AZ::EntityId entityId) = 0; + + //! Returns whether the entity id provided is registered as a container. + virtual bool IsContainer(AZ::EntityId entityId) const = 0; + + //! Sets the open state of the container entity provided. + //! @param entityId The entityId whose open state will be set. + //! @param open True if the container should be opened, false if it should be closed. + //! @return An error message if the operation was invalid, success otherwise. + virtual ContainerEntityOperationResult SetContainerOpenState(AZ::EntityId entityId, bool open) = 0; + + //! If the entity id provided is registered as a container, it returns whether it's open. + //! @note the default value for non-containers is true, so this function can be called without + //! verifying whether the entityId is registered as a container beforehand, since the container's + //! open behavior is exactly the same as the one of a regular entity. + //! @return False if the entityId is registered as a container, and its state is closed. True otherwise. + virtual bool IsContainerOpen(AZ::EntityId entityId) const = 0; + + //! Detects if one of the ancestors of entityId is a closed container entity. + //! @return The highest closed entity container id if any, or entityId otherwise. + virtual AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const = 0; + + }; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h new file mode 100644 index 0000000000..95608feefb --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h @@ -0,0 +1,41 @@ +/* + * 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 changes of state for Container Entities. + class ContainerEntityNotifications + : 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 a container entity status changes. + //! @param entityId The entity whose status has changed. + //! @param open The open state the container was changed to. + virtual void OnContainerEntityStatusChanged([[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] bool open) {} + + protected: + ~ContainerEntityNotifications() = default; + }; + + using ContainerEntityNotificationBus = AZ::EBus; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp new file mode 100644 index 0000000000..0ea2eeb5f6 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp @@ -0,0 +1,120 @@ +/* + * 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 +#include + +namespace AzToolsFramework +{ + void ContainerEntitySystemComponent::Activate() + { + AZ::Interface::Register(this); + } + + void ContainerEntitySystemComponent::Deactivate() + { + AZ::Interface::Unregister(this); + } + + void ContainerEntitySystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + { + } + + void ContainerEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("ContainerEntityService")); + } + + ContainerEntityOperationResult ContainerEntitySystemComponent::RegisterEntityAsContainer(AZ::EntityId entityId) + { + if (IsContainer(entityId)) + { + return AZ::Failure(AZStd::string( + "ContainerEntitySystemComponent error - trying to register entity as container twice.")); + } + + m_containers.insert(entityId); + + return AZ::Success(); + } + + ContainerEntityOperationResult ContainerEntitySystemComponent::UnregisterEntityAsContainer(AZ::EntityId entityId) + { + if (!IsContainer(entityId)) + { + return AZ::Failure(AZStd::string( + "ContainerEntitySystemComponent error - trying to unregister entity that is not a container.")); + } + + m_containers.erase(entityId); + + return AZ::Success(); + } + + bool ContainerEntitySystemComponent::IsContainer(AZ::EntityId entityId) const + { + return m_containers.contains(entityId); + } + + ContainerEntityOperationResult ContainerEntitySystemComponent::SetContainerOpenState(AZ::EntityId entityId, bool open) + { + if (!IsContainer(entityId)) + { + return AZ::Failure(AZStd::string( + "ContainerEntitySystemComponent error - cannot set open state of entity that was not registered as container.")); + } + + if(open) + { + m_openContainers.insert(entityId); + } + else + { + m_openContainers.erase(entityId); + } + + ContainerEntityNotificationBus::Broadcast(&ContainerEntityNotificationBus::Events::OnContainerEntityStatusChanged, entityId, open); + + return AZ::Success(); + } + + bool ContainerEntitySystemComponent::IsContainerOpen(AZ::EntityId entityId) const + { + // If the entity is not a container, it should behave as open. + if(!m_containers.contains(entityId)) + { + return true; + } + + // If the entity is a container, return its state. + return m_openContainers.contains(entityId); + } + + AZ::EntityId ContainerEntitySystemComponent::FindHighestSelectableEntity(AZ::EntityId entityId) const + { + AZ::EntityId highestSelectableEntityId = entityId; + + // Go up the hierarchy until you hit the root + while (entityId.IsValid()) + { + if (!IsContainerOpen(entityId)) + { + // If one of the ancestors is a container and it's closed, keep track of its id. + // We only return of the higher closed container in the hierarchy. + highestSelectableEntityId = entityId; + } + + AZ::TransformBus::EventResult(entityId, entityId, &AZ::TransformBus::Events::GetParentId); + } + + return highestSelectableEntityId; + } + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h new file mode 100644 index 0000000000..18c0fb70c6 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h @@ -0,0 +1,54 @@ +/* + * 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 +{ + //! System Component to track Container Entity registration and open state. + //! An entity registered as Container is just like a regular entity when open. If its state is changed + //! to closed, all descendants of the entity will be treated as part of the entity itself. Selecting any + //! descendant will result in the container being selected, and descendants will be hidden until the + //! container is opened. + class ContainerEntitySystemComponent final + : public AZ::Component + , private ContainerEntityInterface + { + public: + AZ_COMPONENT(ContainerEntitySystemComponent, "{74349759-B36B-44A6-B89F-F45D7111DD11}"); + + ContainerEntitySystemComponent() = default; + virtual ~ContainerEntitySystemComponent() = default; + + // AZ::Component overrides ... + void Activate() override; + void Deactivate() override; + + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + + // ContainerEntityInterface overrides ... + ContainerEntityOperationResult RegisterEntityAsContainer(AZ::EntityId entityId) override; + ContainerEntityOperationResult UnregisterEntityAsContainer(AZ::EntityId entityId) override; + bool IsContainer(AZ::EntityId entityId) const override; + ContainerEntityOperationResult SetContainerOpenState(AZ::EntityId entityId, bool open) override; + bool IsContainerOpen(AZ::EntityId entityId) const override; + AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const override; + + private: + AZStd::unordered_set m_containers; //!< All entities in this set are containers. + AZStd::unordered_set m_openContainers; //!< All entities in this set are open containers. + }; + +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 4e58bbdf05..9ffc18af5a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -35,6 +36,30 @@ namespace AzToolsFramework::Prefab EditorEntityContextNotificationBus::Handler::BusDisconnect(); } + void PrefabFocusHandler::Initialize() + { + m_containerEntityInterface = AZ::Interface::Get(); + AZ_Assert( + m_containerEntityInterface, + "Prefab - PrefabFocusHandler - " + "Container Entity Interface could not be found. " + "Check that it is being correctly initialized."); + + m_focusModeInterface = AZ::Interface::Get(); + AZ_Assert( + m_focusModeInterface, + "Prefab - PrefabFocusHandler - " + "Focus Mode Interface could not be found. " + "Check that it is being correctly initialized."); + + m_instanceEntityMapperInterface = AZ::Interface::Get(); + AZ_Assert( + m_instanceEntityMapperInterface, + "Prefab - PrefabFocusHandler - " + "Instance Entity Mapper Interface could not be found. " + "Check that it is being correctly initialized."); + } + PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) { InstanceOptionalReference focusedInstance; @@ -44,7 +69,7 @@ namespace AzToolsFramework::Prefab PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); - if(!prefabEditorEntityOwnershipInterface) + if (!prefabEditorEntityOwnershipInterface) { return AZ::Failure(AZStd::string("Could not focus on root prefab instance - internal error " "(PrefabEditorEntityOwnershipInterface unavailable).")); @@ -79,8 +104,16 @@ namespace AzToolsFramework::Prefab return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); } + if (!m_isInitialized) + { + Initialize(); + } + if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get()) { + // Close all container entities in the old path + CloseInstanceContainers(m_instanceFocusVector); + m_focusedInstance = focusedInstance; m_focusedTemplateId = focusedInstance->get().GetTemplateId(); @@ -103,12 +136,14 @@ namespace AzToolsFramework::Prefab } // Focus on the descendants of the container entity - if (FocusModeInterface* focusModeInterface = AZ::Interface::Get()) - { - focusModeInterface->SetFocusRoot(containerEntityId); - } + m_focusModeInterface->SetFocusRoot(containerEntityId); + // Refresh path variables RefreshInstanceFocusList(); + + // Open all container entities in the new path + OpenInstanceContainers(m_instanceFocusVector); + PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); } @@ -156,6 +191,11 @@ namespace AzToolsFramework::Prefab void PrefabFocusHandler::OnEntityStreamLoadSuccess() { + if (!m_isInitialized) + { + Initialize(); + } + // Focus on the root prefab (AZ::EntityId() will default to it) FocusOnOwningPrefab(AZ::EntityId()); } @@ -184,4 +224,26 @@ namespace AzToolsFramework::Prefab } } + void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector& instances) const + { + for (const InstanceOptionalReference& instance : instances) + { + if (instance.has_value()) + { + m_containerEntityInterface->SetContainerOpenState(instance->get().GetContainerEntityId(), true); + } + } + } + + void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector& instances) const + { + for (const InstanceOptionalReference& instance : instances) + { + if (instance.has_value()) + { + m_containerEntityInterface->SetContainerOpenState(instance->get().GetContainerEntityId(), false); + } + } + } + } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 2ccec36882..2f631f772d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -15,6 +15,12 @@ #include #include +namespace AzToolsFramework +{ + class ContainerEntityInterface; + class FocusModeInterface; +} + namespace AzToolsFramework::Prefab { class InstanceEntityMapperInterface; @@ -30,6 +36,8 @@ namespace AzToolsFramework::Prefab PrefabFocusHandler(); ~PrefabFocusHandler(); + void Initialize(); + // PrefabFocusInterface overrides ... PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override; PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override; @@ -46,12 +54,19 @@ namespace AzToolsFramework::Prefab PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); void RefreshInstanceFocusList(); + void OpenInstanceContainers(const AZStd::vector& instances) const; + void CloseInstanceContainers(const AZStd::vector& instances) const; + InstanceOptionalReference m_focusedInstance; TemplateId m_focusedTemplateId; AZStd::vector m_instanceFocusVector; AZ::IO::Path m_instanceFocusPath; - InstanceEntityMapperInterface* m_instanceEntityMapperInterface; + ContainerEntityInterface* m_containerEntityInterface = nullptr; + FocusModeInterface* m_focusModeInterface = nullptr; + InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; + + bool m_isInitialized = false; }; } // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp index a0da20ebf0..3cdcade1b0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp @@ -37,51 +37,71 @@ namespace AzToolsFramework return m_handlerId; } - QString EditorEntityUiHandlerBase::GenerateItemInfoString(AZ::EntityId /*entityId*/) const + QString EditorEntityUiHandlerBase::GenerateItemInfoString([[maybe_unused]] AZ::EntityId entityId) const { return QString(); } - QString EditorEntityUiHandlerBase::GenerateItemTooltip(AZ::EntityId /*entityId*/) const + QString EditorEntityUiHandlerBase::GenerateItemTooltip([[maybe_unused]] AZ::EntityId entityId) const { return QString(); } - QIcon EditorEntityUiHandlerBase::GenerateItemIcon(AZ::EntityId /*entityId*/) const + QIcon EditorEntityUiHandlerBase::GenerateItemIcon([[maybe_unused]] AZ::EntityId entityId) const { return QIcon(); } - bool EditorEntityUiHandlerBase::CanToggleLockVisibility(AZ::EntityId /*entityId*/) const + bool EditorEntityUiHandlerBase::CanToggleLockVisibility([[maybe_unused]] AZ::EntityId entityId) const { return true; } - bool EditorEntityUiHandlerBase::CanRename(AZ::EntityId /*entityId*/) const + bool EditorEntityUiHandlerBase::CanRename([[maybe_unused]] AZ::EntityId entityId) const { return true; } - void EditorEntityUiHandlerBase::PaintItemBackground(QPainter* /*painter*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const + void EditorEntityUiHandlerBase::PaintItemBackground( + [[maybe_unused]] QPainter* painter, + [[maybe_unused]] const QStyleOptionViewItem& option, + [[maybe_unused]] const QModelIndex& index) const { } - void EditorEntityUiHandlerBase::PaintDescendantBackground(QPainter* /*painter*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/, - const QModelIndex& /*descendantIndex*/) const + void EditorEntityUiHandlerBase::PaintDescendantBackground( + [[maybe_unused]] QPainter* painter, + [[maybe_unused]] const QStyleOptionViewItem& option, + [[maybe_unused]] const QModelIndex& index, + [[maybe_unused]] const QModelIndex& descendantIndex) const { } - void EditorEntityUiHandlerBase::PaintDescendantBranchBackground(QPainter* /*painter*/, const QTreeView* /*view*/, const QRect& /*rect*/, - const QModelIndex& /*index*/, const QModelIndex& /*descendantIndex*/) const + void EditorEntityUiHandlerBase::PaintDescendantBranchBackground( + [[maybe_unused]] QPainter* painter, + [[maybe_unused]] const QTreeView* view, + [[maybe_unused]] const QRect& rect, + [[maybe_unused]] const QModelIndex& index, + [[maybe_unused]] const QModelIndex& descendantIndex) const { } - void EditorEntityUiHandlerBase::PaintItemForeground(QPainter* /*painter*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const + void EditorEntityUiHandlerBase::PaintItemForeground( + [[maybe_unused]] QPainter* painter, + [[maybe_unused]] const QStyleOptionViewItem& option, + [[maybe_unused]] const QModelIndex& index) const { } - void EditorEntityUiHandlerBase::PaintDescendantForeground(QPainter* /*painter*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/, - const QModelIndex& /*descendantIndex*/) const + void EditorEntityUiHandlerBase::PaintDescendantForeground( + [[maybe_unused]] QPainter* painter, + [[maybe_unused]] const QStyleOptionViewItem& option, + [[maybe_unused]] const QModelIndex& index, + [[maybe_unused]] const QModelIndex& descendantIndex) const + { + } + + void EditorEntityUiHandlerBase::OnDoubleClick([[maybe_unused]] AZ::EntityId entityId) const { } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h index c37b099272..9554ad01fc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h @@ -61,6 +61,9 @@ namespace AzToolsFramework virtual void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QModelIndex& descendantIndex) const; + //! Triggered when the entity is double clicked in the Outliner. + virtual void OnDoubleClick(AZ::EntityId entityId) const; + private: EditorEntityUiHandlerId m_handlerId = 0; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index 2872c1bce3..234696ff2a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -88,6 +88,7 @@ namespace AzToolsFramework EntityOutlinerListModel::~EntityOutlinerListModel() { + ContainerEntityNotificationBus::Handler::BusDisconnect(); EditorEntityInfoNotificationBus::Handler::BusDisconnect(); EditorEntityContextNotificationBus::Handler::BusDisconnect(); ToolsApplicationEvents::Bus::Handler::BusDisconnect(); @@ -105,6 +106,12 @@ namespace AzToolsFramework EntityCompositionNotificationBus::Handler::BusConnect(); AZ::EntitySystemBus::Handler::BusConnect(); + AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult( + editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId); + + ContainerEntityNotificationBus::Handler::BusConnect(editorEntityContextId); + m_editorEntityUiInterface = AZ::Interface::Get(); AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerListModel requires a EditorEntityUiInterface instance on Initialize."); @@ -1333,12 +1340,26 @@ namespace AzToolsFramework emit EnableSelectionUpdates(true); } - void EntityOutlinerListModel::OnEntityRuntimeActivationChanged(AZ::EntityId entityId, bool activeOnStart) + void EntityOutlinerListModel::OnEntityRuntimeActivationChanged(AZ::EntityId entityId, [[maybe_unused]] bool activeOnStart) { - AZ_UNUSED(activeOnStart); QueueEntityUpdate(entityId); } + void EntityOutlinerListModel::OnContainerEntityStatusChanged(AZ::EntityId entityId, [[maybe_unused]] bool open) + { + QModelIndex changedIndex = GetIndexFromEntity(entityId); + + // Trigger a refresh of all direct children so that they can be shown or hidden appropriately. + int numChildren = rowCount(changedIndex); + if (numChildren > 0) + { + emit dataChanged(index(0, 0, changedIndex), index(numChildren - 1, ColumnCount - 1, changedIndex)); + } + + // Always expand containers + QueueEntityToExpand(entityId, true); + } + void EntityOutlinerListModel::OnEntityInfoUpdatedRemoveChildBegin([[maybe_unused]] AZ::EntityId parentId, [[maybe_unused]] AZ::EntityId childId) { //add/remove operations trigger selection change signals which assert and break undo/redo operations in progress in inspector etc. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx index 4b13aa6d27..7b946c6304 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -54,6 +55,7 @@ namespace AzToolsFramework , private EntityCompositionNotificationBus::Handler , private EditorEntityRuntimeActivationChangeNotificationBus::Handler , private AZ::EntitySystemBus::Handler + , private ContainerEntityNotificationBus::Handler { Q_OBJECT; @@ -216,6 +218,9 @@ namespace AzToolsFramework // EditorEntityRuntimeActivationChangeNotificationBus::Handler void OnEntityRuntimeActivationChanged(AZ::EntityId entityId, bool activeOnStart) override; + // ContainerEntityNotificationBus overrides ... + void OnContainerEntityStatusChanged(AZ::EntityId entityId, bool open) override; + // Drag/Drop of components from Component Palette. bool dropMimeDataComponentPalette(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.cpp index 841a01f5e5..600ca284f4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include "EntityOutlinerListModel.hxx" namespace AzToolsFramework @@ -19,6 +21,10 @@ namespace AzToolsFramework EntityOutlinerSortFilterProxyModel::EntityOutlinerSortFilterProxyModel(QObject* pParent) : QSortFilterProxyModel(pParent) { + m_containerEntityInterface = AZ::Interface::Get(); + AZ_Assert( + m_containerEntityInterface != nullptr, + "EntityOutlinerContainerProxyModel requires a ContainerEntityInterface instance on construction."); } void EntityOutlinerSortFilterProxyModel::UpdateFilter() @@ -26,8 +32,24 @@ namespace AzToolsFramework invalidateFilter(); } + void EntityOutlinerSortFilterProxyModel::setSourceModel(QAbstractItemModel* sourceModel) + { + QSortFilterProxyModel::setSourceModel(sourceModel); + + m_listModel = qobject_cast(sourceModel); + AZ_Assert(m_listModel != nullptr, "EntityOutlinerContainerProxyModel requires an EntityOutlinerListModel as its source ."); + } + bool EntityOutlinerSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { + // Retrieve the entityId of the parent entity + AZ::EntityId parentEntityId = m_listModel->GetEntityFromIndex(sourceParent); + + if(!m_containerEntityInterface->IsContainerOpen(parentEntityId)) + { + return false; + } + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); QVariant visibilityData = sourceModel()->data(index, EntityOutlinerListModel::VisibilityRole); return visibilityData.isValid() ? visibilityData.toBool() : true; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.hxx index b7e1a3f869..c6e1410688 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerSortFilterProxyModel.hxx @@ -19,11 +19,12 @@ namespace AzToolsFramework { + class ContainerEntityInterface; + class EntityOutlinerListModel; - /*! - * Enables the Outliner to filter entries based on search string. - * Enables the Outliner to do custom sorting on entries. - */ + //! Enables the Outliner to filter entries based on search string. + //! Enables the Outliner to do custom sorting on entries. + //! Enforces the correct rendering for container entities. class EntityOutlinerSortFilterProxyModel : public QSortFilterProxyModel { @@ -37,12 +38,15 @@ namespace AzToolsFramework void UpdateFilter(); // Qt overrides + void setSourceModel(QAbstractItemModel* sourceModel) override; bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; void sort(int column, Qt::SortOrder order) override; private: QString m_filterName; + EntityOutlinerListModel* m_listModel = nullptr; + ContainerEntityInterface* m_containerEntityInterface = nullptr; }; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 689e5b5dc4..8f90d2c0d7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -198,6 +198,7 @@ namespace AzToolsFramework m_proxyModel = aznew EntityOutlinerSortFilterProxyModel(this); m_proxyModel->setSourceModel(m_listModel); + m_gui->m_objectTree->setModel(m_proxyModel); // Link up signals for informing the model of tree changes using the proxy as an intermediary @@ -905,8 +906,12 @@ namespace AzToolsFramework } } - void EntityOutlinerWidget::OnTreeItemDoubleClicked(const QModelIndex& /*index*/) + void EntityOutlinerWidget::OnTreeItemDoubleClicked(const QModelIndex& index) { + if (AZ::EntityId entityId = GetEntityIdFromIndex(index); auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId)) + { + entityUiHandler->OnDoubleClick(entityId); + } } void EntityOutlinerWidget::OnTreeItemExpanded(const QModelIndex& index) @@ -1142,7 +1147,7 @@ namespace AzToolsFramework { QTimer::singleShot(1, this, [this]() { m_gui->m_objectTree->setUpdatesEnabled(true); - m_gui->m_objectTree->expand(m_proxyModel->index(0,0)); + m_gui->m_objectTree->expandToDepth(0); }); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx index 78aced3587..78927359f2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx @@ -41,6 +41,7 @@ namespace AzToolsFramework { class EditorEntityUiInterface; class EntityOutlinerListModel; + class EntityOutlinerContainerProxyModel; class EntityOutlinerSortFilterProxyModel; namespace EntityOutliner @@ -117,6 +118,7 @@ namespace AzToolsFramework Ui::EntityOutlinerWidgetUI* m_gui; EntityOutlinerListModel* m_listModel; + EntityOutlinerContainerProxyModel* m_containerModel; EntityOutlinerSortFilterProxyModel* m_proxyModel; AZ::u64 m_selectionContextId; AZStd::vector m_selectedEntityIds; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 34152f8287..62842e18d5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,7 @@ namespace AzToolsFramework { namespace Prefab { - + ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr; EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr; PrefabFocusInterface* PrefabIntegrationManager::s_prefabFocusInterface = nullptr; PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr; @@ -89,6 +90,13 @@ namespace AzToolsFramework PrefabIntegrationManager::PrefabIntegrationManager() { + s_containerEntityInterface = AZ::Interface::Get(); + if (s_containerEntityInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get ContainerEntityInterface on PrefabIntegrationManager construction."); + return; + } + s_editorEntityUiInterface = AZ::Interface::Get(); if (s_editorEntityUiInterface == nullptr) { @@ -1057,6 +1065,7 @@ namespace AzToolsFramework void PrefabIntegrationManager::OnPrefabComponentActivate(AZ::EntityId entityId) { + // Register entity to appropriate UI Handler for UI overrides if (s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId)) { s_editorEntityUiInterface->RegisterEntity(entityId, m_levelRootUiHandler.GetHandlerId()); @@ -1064,11 +1073,32 @@ namespace AzToolsFramework else { s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId()); + + bool prefabWipFeaturesEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); + + if (prefabWipFeaturesEnabled) + { + // Register entity as a container + s_containerEntityInterface->RegisterEntityAsContainer(entityId); + } } } void PrefabIntegrationManager::OnPrefabComponentDeactivate(AZ::EntityId entityId) { + bool prefabWipFeaturesEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); + + if (prefabWipFeaturesEnabled && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId)) + { + // Unregister entity as a container + s_containerEntityInterface->UnregisterEntityAsContainer(entityId); + } + + // Unregister entity from UI Handler s_editorEntityUiInterface->UnregisterEntity(entityId); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index 6f66b1f514..44e30b2013 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -26,6 +26,8 @@ namespace AzToolsFramework { + class ContainerEntityInterface; + namespace Prefab { class PrefabFocusInterface; @@ -134,6 +136,7 @@ namespace AzToolsFramework static const AZStd::string s_prefabFileExtension; + static ContainerEntityInterface* s_containerEntityInterface; static EditorEntityUiInterface* s_editorEntityUiInterface; static PrefabFocusInterface* s_prefabFocusInterface; static PrefabLoaderInterface* s_prefabLoaderInterface; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index c802850c4b..7d1f3485aa 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -8,6 +8,8 @@ #include +#include + #include #include #include @@ -317,4 +319,17 @@ namespace AzToolsFramework return Internal_GetLastVisibleChild(model, lastChild); } + + void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const + { + bool prefabWipFeaturesEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); + + if (prefabWipFeaturesEnabled) + { + // Focus on this prefab + m_prefabFocusInterface->FocusOnOwningPrefab(entityId); + } + } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h index 547c100eb1..bb7168f409 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h @@ -29,13 +29,14 @@ namespace AzToolsFramework PrefabUiHandler(); ~PrefabUiHandler() override = default; - // EditorEntityUiHandler... + // EditorEntityUiHandler overrides ... QString GenerateItemInfoString(AZ::EntityId entityId) const override; QString GenerateItemTooltip(AZ::EntityId entityId) const override; QIcon GenerateItemIcon(AZ::EntityId entityId) const override; void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QModelIndex& descendantIndex) const override; + void OnDoubleClick(AZ::EntityId entityId) const override; private: Prefab::PrefabFocusInterface* m_prefabFocusInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp index 57e6c9ff7a..3ce350287f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorHelpers.cpp @@ -13,8 +13,9 @@ #include #include #include -#include #include +#include +#include #include #include #include @@ -191,6 +192,14 @@ namespace AzToolsFramework return AZ::EntityId(); } + // Container Entity support - if the entity that is being selected is part of a closed container, + // change the selection to the container instead. + ContainerEntityInterface* containerEntityInterface = AZ::Interface::Get(); + if (containerEntityInterface) + { + return containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor); + } + return entityIdUnderCursor; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index f09d4f9b72..0866b225c1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -114,6 +114,10 @@ set(FILES Component/EditorLevelComponentAPIBus.h Component/EditorLevelComponentAPIComponent.cpp Component/EditorLevelComponentAPIComponent.h + ContainerEntity/ContainerEntityInterface.h + ContainerEntity/ContainerEntityNotificationBus.h + ContainerEntity/ContainerEntitySystemComponent.cpp + ContainerEntity/ContainerEntitySystemComponent.h Editor/EditorContextMenuBus.h Editor/EditorSettingsAPIBus.h Entity/EditorEntityStartStatus.h