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
Danilo Aimini 4 years ago committed by GitHub
parent df419a0990
commit bb8971a3ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,7 @@
#include <AzToolsFramework/ToolsComponents/EditorAssetMimeDataContainer.h>
#include <AzToolsFramework/ToolsComponents/ComponentAssetMimeDataContainer.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
@ -251,6 +252,7 @@ namespace AzToolsFramework
azrtti_typeid<EditorEntityContextComponent>(),
azrtti_typeid<Components::EditorEntityUiSystemComponent>(),
azrtti_typeid<FocusModeSystemComponent>(),
azrtti_typeid<ContainerEntitySystemComponent>(),
azrtti_typeid<SliceMetadataEntityContextComponent>(),
azrtti_typeid<Prefab::PrefabSystemComponent>(),
azrtti_typeid<EditorEntityFixupComponent>(),

@ -16,6 +16,7 @@
#include <AzToolsFramework/AssetBundle/AssetBundleComponent.h>
#include <AzToolsFramework/Component/EditorComponentAPIComponent.h>
#include <AzToolsFramework/Component/EditorLevelComponentAPIComponent.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
#include <AzToolsFramework/Entity/EditorEntityActionComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityFixupComponent.h>
@ -70,6 +71,7 @@ namespace AzToolsFramework
Components::EditorSelectionAccentSystemComponent::CreateDescriptor(),
EditorEntityContextComponent::CreateDescriptor(),
EditorEntityFixupComponent::CreateDescriptor(),
ContainerEntitySystemComponent::CreateDescriptor(),
FocusModeSystemComponent::CreateDescriptor(),
SliceMetadataEntityContextComponent::CreateDescriptor(),
SliceRequestComponent::CreateDescriptor(),

@ -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

@ -8,6 +8,7 @@
#include <AzToolsFramework/Prefab/PrefabFocusHandler.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
@ -35,6 +36,30 @@ namespace AzToolsFramework::Prefab
EditorEntityContextNotificationBus::Handler::BusDisconnect();
}
void PrefabFocusHandler::Initialize()
{
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::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<FocusModeInterface>::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<InstanceEntityMapperInterface>::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<PrefabEditorEntityOwnershipInterface>::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<FocusModeInterface>::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<InstanceOptionalReference>& instances) const
{
for (const InstanceOptionalReference& instance : instances)
{
if (instance.has_value())
{
m_containerEntityInterface->SetContainerOpenState(instance->get().GetContainerEntityId(), true);
}
}
}
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{
for (const InstanceOptionalReference& instance : instances)
{
if (instance.has_value())
{
m_containerEntityInterface->SetContainerOpenState(instance->get().GetContainerEntityId(), false);
}
}
}
} // namespace AzToolsFramework::Prefab

@ -15,6 +15,12 @@
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
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<InstanceOptionalReference>& instances) const;
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
InstanceOptionalReference m_focusedInstance;
TemplateId m_focusedTemplateId;
AZStd::vector<InstanceOptionalReference> 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

@ -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
{
}

@ -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;
};

@ -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<AzToolsFramework::EditorEntityUiInterface>::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.

@ -17,6 +17,7 @@
#include <AzToolsFramework/API/EntityCompositionNotificationBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/EditorEntityRuntimeActivationBus.h>
@ -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);

@ -11,6 +11,8 @@
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/Entity.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include "EntityOutlinerListModel.hxx"
namespace AzToolsFramework
@ -19,6 +21,10 @@ namespace AzToolsFramework
EntityOutlinerSortFilterProxyModel::EntityOutlinerSortFilterProxyModel(QObject* pParent)
: QSortFilterProxyModel(pParent)
{
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::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<EntityOutlinerListModel*>(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;

@ -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;
};
}

@ -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);
});
}

@ -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<AZ::EntityId> m_selectedEntityIds;

@ -22,6 +22,7 @@
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
@ -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<ContainerEntityInterface>::Get();
if (s_containerEntityInterface == nullptr)
{
AZ_Assert(false, "Prefab - could not get ContainerEntityInterface on PrefabIntegrationManager construction.");
return;
}
s_editorEntityUiInterface = AZ::Interface<EditorEntityUiInterface>::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);
}

@ -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;

@ -8,6 +8,8 @@
#include <AzToolsFramework/UI/Prefab/PrefabUiHandler.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
@ -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);
}
}
}

@ -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;

@ -13,8 +13,9 @@
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/API/EditorViewportIconDisplayInterface.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorEntityIconComponentBus.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
@ -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<ContainerEntityInterface>::Get();
if (containerEntityInterface)
{
return containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
}
return entityIdUnderCursor;
}

@ -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

Loading…
Cancel
Save