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>monroegm-disable-blank-issue-2
parent
df419a0990
commit
bb8971a3ad
@ -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 <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
//! Outcome object that returns an error message in case of failure to allow caller to handle internal errors.
|
||||
using ContainerEntityOperationResult = AZ::Outcome<void, AZStd::string>;
|
||||
|
||||
//! 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
|
||||
@ -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 <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContext.h>
|
||||
|
||||
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<ContainerEntityNotifications>;
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
@ -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 <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
|
||||
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
void ContainerEntitySystemComponent::Activate()
|
||||
{
|
||||
AZ::Interface<ContainerEntityInterface>::Register(this);
|
||||
}
|
||||
|
||||
void ContainerEntitySystemComponent::Deactivate()
|
||||
{
|
||||
AZ::Interface<ContainerEntityInterface>::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
|
||||
@ -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 <AzCore/Component/Component.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
|
||||
|
||||
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<AZ::EntityId> m_containers; //!< All entities in this set are containers.
|
||||
AZStd::unordered_set<AZ::EntityId> m_openContainers; //!< All entities in this set are open containers.
|
||||
};
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
Loading…
Reference in New Issue