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