You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1958 lines
76 KiB
C++
1958 lines
76 KiB
C++
/*
|
|
* 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 "UiElementComponent.h"
|
|
|
|
#include <AzCore/Math/Crc.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/DataPatch.h>
|
|
#include <AzCore/Serialization/Utils.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/std/sort.h>
|
|
|
|
#include "UiCanvasComponent.h"
|
|
#include <LyShine/IDraw2d.h>
|
|
#include <LyShine/Bus/UiTransformBus.h>
|
|
#include <LyShine/Bus/UiVisualBus.h>
|
|
#include <LyShine/Bus/UiEditorBus.h>
|
|
#include <LyShine/Bus/UiRenderBus.h>
|
|
#include <LyShine/Bus/UiRenderControlBus.h>
|
|
#include <LyShine/Bus/UiInteractionMaskBus.h>
|
|
#include <LyShine/Bus/UiInteractableBus.h>
|
|
#include <LyShine/Bus/UiEntityContextBus.h>
|
|
#include <LyShine/Bus/UiLayoutManagerBus.h>
|
|
|
|
#include <CryCommon/StlUtils.h>
|
|
|
|
#include "UiTransform2dComponent.h"
|
|
|
|
#include "IConsole.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiElementComponent::UiElementComponent()
|
|
{
|
|
// This is required in order to be able to tell if the element is in the scheduled transform
|
|
// recompute list (intrusive_slist doesn't initialize this except in a debug build)
|
|
m_next = nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiElementComponent::~UiElementComponent()
|
|
{
|
|
// If this element is currently in the list of elements needing a transform recompute then
|
|
// remove it from that list since the element is being destroyed
|
|
if (m_next)
|
|
{
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->UnscheduleElementForTransformRecompute(this);
|
|
}
|
|
else
|
|
{
|
|
m_next = nullptr;
|
|
}
|
|
}
|
|
|
|
// In normal (correct) usage we have nothing to do here.
|
|
// But if a user calls DeleteEntity or just deletes an entity pointer they can delete a UI element
|
|
// and leave its parent with a dangling child pointer.
|
|
// So we report an error in that case and do some recovery code.
|
|
|
|
// If we were being deleted via DestroyElement m_parentId would be invalid
|
|
if (m_parentId.IsValid())
|
|
{
|
|
// Note we do not rely on the m_parent pointer because if the canvas is being unloaded for example the
|
|
// parent entity could already have been deleted. So we use the parent entity Id to try to find the parent.
|
|
AZ::Entity* parent = nullptr;
|
|
EBUS_EVENT_RESULT(parent, AZ::ComponentApplicationBus, FindEntity, m_parentId);
|
|
|
|
// If the parent is found and it is active that suggests something is wrong. When unloading a canvas we
|
|
// deactivate all of the UI elements before any are deleted
|
|
if (parent && parent->GetState() == AZ::Entity::State::Active)
|
|
{
|
|
// As a final check see if this element's parent thinks that this is a child, this is almost certain to be the
|
|
// case if we got here but, if not, there is nothing more to do
|
|
UiElementComponent* parentElementComponent = parent->FindComponent<UiElementComponent>();
|
|
if (parentElementComponent)
|
|
{
|
|
if (parentElementComponent->FindChildByEntityId(GetEntityId()))
|
|
{
|
|
// This is an error, report the error
|
|
AZ_Error("UI", false, "Deleting a UI element entity directly rather than using DestroyElement. Element is named '%s'", m_entity->GetName().c_str());
|
|
|
|
// Attempt to recover by removing this element from the parent's child list
|
|
parentElementComponent->RemoveChild(m_entity);
|
|
|
|
// And recursively delete any child UI elements (like DestroyElement on this element would have done)
|
|
auto childElementComponents = m_childElementComponents;
|
|
for (auto child : childElementComponents)
|
|
{
|
|
// destroy the child
|
|
child->DestroyElement();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::RenderElement(LyShine::IRenderGraph* renderGraph, bool isInGame)
|
|
{
|
|
if (!IsFullyInitialized())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!m_isRenderEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isInGame)
|
|
{
|
|
if (!m_isEnabled)
|
|
{
|
|
// Nothing to do - whole element and all children are disabled
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We are in editing mode (not running the game)
|
|
// Use the UiEditorBus to query any UiEditorComponent on this element to see if this element is
|
|
// hidden in the editor
|
|
bool isVisible = true;
|
|
EBUS_EVENT_ID_RESULT(isVisible, GetEntityId(), UiEditorBus, GetIsVisible);
|
|
if (!isVisible)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If a component is connected to the UiRenderControl bus then we give control of rendering this element
|
|
// and its children to that component, otherwise follow the standard render path
|
|
if (m_renderControlInterface)
|
|
{
|
|
// give control of rendering this element and its children to the render control component on this element
|
|
m_renderControlInterface->Render(renderGraph, this, m_renderInterface, static_cast<int>(m_childElementComponents.size()), isInGame);
|
|
}
|
|
else
|
|
{
|
|
// render any component on this element connected to the UiRenderBus
|
|
if (m_renderInterface)
|
|
{
|
|
m_renderInterface->Render(renderGraph);
|
|
}
|
|
|
|
// now render child elements
|
|
int numChildren = static_cast<int>(m_childElementComponents.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
GetChildElementComponent(i)->RenderElement(renderGraph, isInGame);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
LyShine::ElementId UiElementComponent::GetElementId()
|
|
{
|
|
return m_elementId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
LyShine::NameType UiElementComponent::GetName()
|
|
{
|
|
return GetEntity() ? GetEntity()->GetName() : "";
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::GetCanvasEntityId()
|
|
{
|
|
return m_canvas ? m_canvas->GetEntityId() : AZ::EntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::GetParent()
|
|
{
|
|
return m_parent;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::GetParentEntityId()
|
|
{
|
|
return m_parentId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
int UiElementComponent::GetNumChildElements()
|
|
{
|
|
return static_cast<int>(m_childEntityIdOrder.size());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::GetChildElement(int index)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
if (index >= 0 && index < m_childEntityIdOrder.size())
|
|
{
|
|
if (AreChildPointersValid())
|
|
{
|
|
childEntity = GetChildElementComponent(index)->GetEntity();
|
|
}
|
|
else
|
|
{
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, m_childEntityIdOrder[index].m_entityId);
|
|
}
|
|
}
|
|
return childEntity;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::GetChildEntityId(int index)
|
|
{
|
|
AZ::EntityId childEntityId;
|
|
if (index >= 0 && index < m_childEntityIdOrder.size())
|
|
{
|
|
childEntityId = m_childEntityIdOrder[index].m_entityId;
|
|
}
|
|
return childEntityId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
UiElementInterface* UiElementComponent::GetChildElementInterface(int index)
|
|
{
|
|
return GetChildElementComponent(index);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
int UiElementComponent::GetIndexOfChild(const AZ::Entity* child)
|
|
{
|
|
AZ::EntityId childEntityId = child->GetId();
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
if (m_childEntityIdOrder[i].m_entityId == childEntityId)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
AZ_Error("UI", false, "The given entity is not a child of this UI element");
|
|
return -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
int UiElementComponent::GetIndexOfChildByEntityId(AZ::EntityId childId)
|
|
{
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
if (m_childEntityIdOrder[i].m_entityId == childId)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
AZ_Error("UI", false, "The given entity is not a child of this UI element");
|
|
return -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
LyShine::EntityArray UiElementComponent::GetChildElements()
|
|
{
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
LyShine::EntityArray children;
|
|
children.reserve(numChildren);
|
|
|
|
// This is one of the rare functions that needs to work before FixupPostLoad has been called because it is called
|
|
// from OnSliceInstantiated, so only use m_childElementComponents if it is setup
|
|
if (AreChildPointersValid())
|
|
{
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
children.push_back(GetChildElementComponent(i)->GetEntity());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto& childOrderEntry : m_childEntityIdOrder)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, childOrderEntry.m_entityId);
|
|
if (childEntity)
|
|
{
|
|
children.push_back(childEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZStd::vector<AZ::EntityId> UiElementComponent::GetChildEntityIds()
|
|
{
|
|
AZStd::vector<AZ::EntityId> children;
|
|
for (auto& child : m_childEntityIdOrder)
|
|
{
|
|
children.push_back(child.m_entityId);
|
|
}
|
|
return children;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::CreateChildElement(const LyShine::NameType& name)
|
|
{
|
|
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
|
|
EBUS_EVENT_ID_RESULT(contextId, GetEntityId(), AzFramework::EntityIdContextQueryBus, GetOwningContextId);
|
|
|
|
AZ::Entity* child = nullptr;
|
|
EBUS_EVENT_ID_RESULT(child, contextId, UiEntityContextRequestBus, CreateUiEntity, name.c_str());
|
|
AZ_Assert(child, "Failed to create child entity");
|
|
|
|
child->Deactivate(); // deactivate so that we can add components
|
|
|
|
UiElementComponent* elementComponent = child->CreateComponent<UiElementComponent>();
|
|
AZ_Assert(elementComponent, "Failed to create UiElementComponent");
|
|
|
|
elementComponent->m_canvas = m_canvas;
|
|
elementComponent->SetParentReferences(m_entity, this);
|
|
elementComponent->m_elementId = m_canvas->GenerateId();
|
|
|
|
child->Activate(); // re-activate
|
|
|
|
if (AreChildPointersValid()) // must test before m_childEntityIdOrder.push_back
|
|
{
|
|
m_childElementComponents.push_back(elementComponent);
|
|
}
|
|
m_childEntityIdOrder.push_back({child->GetId(), m_childEntityIdOrder.size()});
|
|
|
|
return child;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::DestroyElement()
|
|
{
|
|
PrepareElementForDestroy();
|
|
|
|
DestroyElementEntity(GetEntityId());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::DestroyElementOnFrameEnd()
|
|
{
|
|
PrepareElementForDestroy();
|
|
|
|
if (m_canvas)
|
|
{
|
|
// Delay deletion of elements to ensure a script canvas can safely destroy its parent
|
|
m_canvas->ScheduleElementDestroy(GetEntityId());
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::Reparent(AZ::Entity* newParent, AZ::Entity* insertBefore)
|
|
{
|
|
if (!newParent)
|
|
{
|
|
if (IsFullyInitialized())
|
|
{
|
|
newParent = GetCanvasComponent()->GetRootElement();
|
|
}
|
|
else
|
|
{
|
|
EmitNotInitializedWarning();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (newParent == GetEntity())
|
|
{
|
|
AZ_Warning("UI", false, "Cannot set an entity's parent to itself")
|
|
return;
|
|
}
|
|
|
|
UiElementComponent* newParentElement = newParent->FindComponent<UiElementComponent>();
|
|
AZ_Assert(newParentElement, "New parent entity has no UiElementComponent");
|
|
|
|
// check if the new parent is in a different canvas if so a reparent is not allowed
|
|
// and the caller should do a CloneElement and DestroyElement
|
|
if (m_canvas != newParentElement->m_canvas)
|
|
{
|
|
AZ_Warning("UI", false, "Reparent: Cannot reparent an element to a different canvas");
|
|
return;
|
|
}
|
|
|
|
if (m_parent)
|
|
{
|
|
// remove from parent
|
|
GetParentElementComponent()->RemoveChild(GetEntity());
|
|
}
|
|
|
|
newParentElement->AddChild(GetEntity(), insertBefore);
|
|
|
|
SetParentReferences(newParent, newParentElement);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::ReparentByEntityId(AZ::EntityId newParent, AZ::EntityId insertBefore)
|
|
{
|
|
AZ::Entity* newParentEntity = nullptr;
|
|
if (newParent.IsValid())
|
|
{
|
|
EBUS_EVENT_RESULT(newParentEntity, AZ::ComponentApplicationBus, FindEntity, newParent);
|
|
AZ_Assert(newParentEntity, "Entity for newParent not found");
|
|
}
|
|
|
|
AZ::Entity* insertBeforeEntity = nullptr;
|
|
if (insertBefore.IsValid())
|
|
{
|
|
EBUS_EVENT_RESULT(insertBeforeEntity, AZ::ComponentApplicationBus, FindEntity, insertBefore);
|
|
AZ_Assert(insertBeforeEntity, "Entity for insertBefore not found");
|
|
}
|
|
|
|
Reparent(newParentEntity, insertBeforeEntity);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::AddToParentAtIndex(AZ::Entity* newParent, int index)
|
|
{
|
|
AZ_Assert(!m_parent, "Element already has a parent");
|
|
|
|
if (!newParent)
|
|
{
|
|
if (IsFullyInitialized())
|
|
{
|
|
newParent = GetCanvasComponent()->GetRootElement();
|
|
}
|
|
else
|
|
{
|
|
EmitNotInitializedWarning();
|
|
return;
|
|
}
|
|
}
|
|
|
|
UiElementComponent* newParentElement = newParent->FindComponent<UiElementComponent>();
|
|
AZ_Assert(newParentElement, "New parent entity has no UiElementComponent");
|
|
|
|
AZ::Entity* insertBefore = nullptr;
|
|
if (index >= 0 && index < newParentElement->GetNumChildElements())
|
|
{
|
|
insertBefore = newParentElement->GetChildElement(index);
|
|
}
|
|
|
|
newParentElement->AddChild(GetEntity(), insertBefore);
|
|
|
|
SetParentReferences(newParent, newParentElement);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::RemoveFromParent()
|
|
{
|
|
if (m_parent)
|
|
{
|
|
// remove from parent
|
|
GetParentElementComponent()->RemoveChild(GetEntity());
|
|
|
|
SetParentReferences(nullptr, nullptr);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::FindFrontmostChildContainingPoint(AZ::Vector2 point, bool isInGame)
|
|
{
|
|
if (!IsFullyInitialized())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
AZ::Entity* matchElem = nullptr;
|
|
|
|
// this traverses all of the elements in reverse hierarchy order and returns the first one that
|
|
// is containing the point.
|
|
// If necessary, this could be optimized using a spatial partitioning data structure.
|
|
for (int i = static_cast<int>(m_childEntityIdOrder.size() - 1); !matchElem && i >= 0; i--)
|
|
{
|
|
AZ::EntityId child = m_childEntityIdOrder[i].m_entityId;
|
|
|
|
if (!isInGame)
|
|
{
|
|
// We are in editing mode (not running the game)
|
|
// Use the UiEditorBus to query any UiEditorComponent on this element to see if this element is
|
|
// hidden in the editor
|
|
bool isVisible = true;
|
|
EBUS_EVENT_ID_RESULT(isVisible, child, UiEditorBus, GetIsVisible);
|
|
if (!isVisible)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
UiElementComponent* childElementComponent = GetChildElementComponent(i);
|
|
|
|
// Check children of this child first
|
|
// child elements do not have to be contained in the parent element's bounds
|
|
matchElem = childElementComponent->FindFrontmostChildContainingPoint(point, isInGame);
|
|
|
|
if (!matchElem)
|
|
{
|
|
bool isSelectable = true;
|
|
if (!isInGame)
|
|
{
|
|
// We are in editing mode (not running the game)
|
|
// Use the UiEditorBus to query any UiEditorComponent on this element to see if this element
|
|
// can be selected in the editor
|
|
EBUS_EVENT_ID_RESULT(isSelectable, child, UiEditorBus, GetIsSelectable);
|
|
}
|
|
|
|
if (isSelectable)
|
|
{
|
|
// if no children of this child matched then check if point is in bounds of this child element
|
|
bool isPointInRect = childElementComponent->GetTransform2dComponent()->IsPointInRect(point);
|
|
if (isPointInRect)
|
|
{
|
|
matchElem = childElementComponent->GetEntity();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return matchElem;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
LyShine::EntityArray UiElementComponent::FindAllChildrenIntersectingRect(const AZ::Vector2& bound0, const AZ::Vector2& bound1, bool isInGame)
|
|
{
|
|
LyShine::EntityArray result;
|
|
|
|
if (!IsFullyInitialized())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// this traverses all of the elements in hierarchy order
|
|
for (int i = 0; i < m_childEntityIdOrder.size(); ++i)
|
|
{
|
|
AZ::EntityId child = m_childEntityIdOrder[i].m_entityId;
|
|
|
|
if (!isInGame)
|
|
{
|
|
// We are in editing mode (not running the game)
|
|
// Use the UiEditorBus to query any UiEditorComponent on this element to see if this element is
|
|
// hidden in the editor
|
|
bool isVisible = true;
|
|
EBUS_EVENT_ID_RESULT(isVisible, child, UiEditorBus, GetIsVisible);
|
|
if (!isVisible)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
UiElementComponent* childElementComponent = GetChildElementComponent(i);
|
|
|
|
// Check children of this child first
|
|
// child elements do not have to be contained in the parent element's bounds
|
|
LyShine::EntityArray childMatches = childElementComponent->FindAllChildrenIntersectingRect(bound0, bound1, isInGame);
|
|
result.insert(result.end(),childMatches.begin(),childMatches.end());
|
|
|
|
bool isSelectable = true;
|
|
if (!isInGame)
|
|
{
|
|
// We are in editing mode (not running the game)
|
|
// Use the UiEditorBus to query any UiEditorComponent on this element to see if this element
|
|
// can be selected in the editor
|
|
EBUS_EVENT_ID_RESULT(isSelectable, child, UiEditorBus, GetIsSelectable);
|
|
}
|
|
|
|
if (isSelectable)
|
|
{
|
|
// check if point is in bounds of this child element
|
|
bool isInRect = childElementComponent->GetTransform2dComponent()->BoundsAreOverlappingRect(bound0, bound1);
|
|
if (isInRect)
|
|
{
|
|
result.push_back(childElementComponent->GetEntity());
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::FindInteractableToHandleEvent(AZ::Vector2 point)
|
|
{
|
|
AZ::EntityId result;
|
|
|
|
if (!IsFullyInitialized() || !m_isEnabled)
|
|
{
|
|
// Nothing to do
|
|
return result;
|
|
}
|
|
|
|
// first check the children (in reverse order) since children are in front of parent
|
|
{
|
|
// if this element is masking children at this point then don't check the children
|
|
bool isMasked = false;
|
|
EBUS_EVENT_ID_RESULT(isMasked, GetEntityId(), UiInteractionMaskBus, IsPointMasked, point);
|
|
if (!isMasked)
|
|
{
|
|
for (int i = static_cast<int>(m_childEntityIdOrder.size() - 1); !result.IsValid() && i >= 0; i--)
|
|
{
|
|
result = GetChildElementComponent(i)->FindInteractableToHandleEvent(point);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if no match then check this element
|
|
if (!result.IsValid())
|
|
{
|
|
// if this element has an interactable component and the point is in this element's rect
|
|
if (UiInteractableBus::FindFirstHandler(GetEntityId()))
|
|
{
|
|
bool isInRect = GetTransform2dComponent()->IsPointInRect(point);
|
|
if (isInRect)
|
|
{
|
|
// check if this interactable component is in a state where it can handle an event at the given point
|
|
bool canHandle = false;
|
|
EBUS_EVENT_ID_RESULT(canHandle, GetEntityId(), UiInteractableBus, CanHandleEvent, point);
|
|
if (canHandle)
|
|
{
|
|
result = GetEntityId();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::FindParentInteractableSupportingDrag(AZ::Vector2 point)
|
|
{
|
|
AZ::EntityId result;
|
|
|
|
// if this element has a parent and this element is not completely disabled
|
|
if (m_parent && m_isEnabled)
|
|
{
|
|
AZ::EntityId parentEntity = m_parent->GetId();
|
|
|
|
// if the parent supports drag hand off then return it
|
|
bool supportsDragOffset = false;
|
|
EBUS_EVENT_ID_RESULT(supportsDragOffset, parentEntity, UiInteractableBus, DoesSupportDragHandOff, point);
|
|
if (supportsDragOffset)
|
|
{
|
|
// Make sure the parent is also handling events
|
|
bool handlingEvents = false;
|
|
EBUS_EVENT_ID_RESULT(handlingEvents, parentEntity, UiInteractableBus, IsHandlingEvents);
|
|
supportsDragOffset = handlingEvents;
|
|
}
|
|
|
|
if (supportsDragOffset)
|
|
{
|
|
result = parentEntity;
|
|
}
|
|
else
|
|
{
|
|
// else keep going up the parent links
|
|
result = GetParentElementComponent()->FindParentInteractableSupportingDrag(point);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::FindChildByName(const LyShine::NameType& name)
|
|
{
|
|
AZ::Entity* matchElem = nullptr;
|
|
|
|
if (AreChildPointersValid())
|
|
{
|
|
int numChildren = static_cast<int>(m_childElementComponents.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
AZ::Entity* childEntity = GetChildElementComponent(i)->GetEntity();
|
|
if (name == childEntity->GetName())
|
|
{
|
|
matchElem = childEntity;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto& child : m_childEntityIdOrder)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, child.m_entityId);
|
|
if (childEntity && name == childEntity->GetName())
|
|
{
|
|
matchElem = childEntity;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return matchElem;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::FindDescendantByName(const LyShine::NameType& name)
|
|
{
|
|
AZ::Entity* matchElem = nullptr;
|
|
|
|
if (AreChildPointersValid())
|
|
{
|
|
int numChildren = static_cast<int>(m_childElementComponents.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
UiElementComponent* childElementComponent = GetChildElementComponent(i);
|
|
AZ::Entity* childEntity = childElementComponent->GetEntity();
|
|
|
|
if (name == childEntity->GetName())
|
|
{
|
|
matchElem = childEntity;
|
|
break;
|
|
}
|
|
|
|
matchElem = childElementComponent->FindDescendantByName(name);
|
|
if (matchElem)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto& child : m_childEntityIdOrder)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, child.m_entityId);
|
|
|
|
if (childEntity && name == childEntity->GetName())
|
|
{
|
|
matchElem = childEntity;
|
|
break;
|
|
}
|
|
|
|
EBUS_EVENT_ID_RESULT(matchElem, child.m_entityId, UiElementBus, FindDescendantByName, name);
|
|
if (matchElem)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return matchElem;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::FindChildEntityIdByName(const LyShine::NameType& name)
|
|
{
|
|
AZ::Entity* childEntity = FindChildByName(name);
|
|
return childEntity ? childEntity->GetId() : AZ::EntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::FindDescendantEntityIdByName(const LyShine::NameType& name)
|
|
{
|
|
AZ::Entity* childEntity = FindDescendantByName(name);
|
|
return childEntity ? childEntity->GetId() : AZ::EntityId();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::FindChildByEntityId(AZ::EntityId id)
|
|
{
|
|
AZ::Entity* matchElem = nullptr;
|
|
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
if (id == m_childEntityIdOrder[i].m_entityId)
|
|
{
|
|
if (AreChildPointersValid())
|
|
{
|
|
matchElem = GetChildElementComponent(i)->GetEntity();
|
|
}
|
|
else
|
|
{
|
|
EBUS_EVENT_RESULT(matchElem, AZ::ComponentApplicationBus, FindEntity, id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return matchElem;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::Entity* UiElementComponent::FindDescendantById(LyShine::ElementId id)
|
|
{
|
|
if (id == m_elementId)
|
|
{
|
|
return GetEntity();
|
|
}
|
|
|
|
AZ::Entity* match = nullptr;
|
|
|
|
if (AreChildPointersValid())
|
|
{
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
for (int i = 0; !match && i < numChildren; ++i)
|
|
{
|
|
match = GetChildElementComponent(i)->FindDescendantById(id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto iter = m_childEntityIdOrder.begin(); !match && iter != m_childEntityIdOrder.end(); iter++)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(match, iter->m_entityId, UiElementBus, FindDescendantById, id);
|
|
}
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::FindDescendantElements(AZStd::function<bool(const AZ::Entity*)> predicate, LyShine::EntityArray& result)
|
|
{
|
|
if (AreChildPointersValid())
|
|
{
|
|
int numChildren = static_cast<int>(m_childElementComponents.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
UiElementComponent* childElementComponent = GetChildElementComponent(i);
|
|
|
|
AZ::Entity* childEntity = childElementComponent->GetEntity();
|
|
if (predicate(childEntity))
|
|
{
|
|
result.push_back(childEntity);
|
|
}
|
|
|
|
childElementComponent->FindDescendantElements(predicate, result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto& child : m_childEntityIdOrder)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, child.m_entityId);
|
|
if (childEntity && predicate(childEntity))
|
|
{
|
|
result.push_back(childEntity);
|
|
}
|
|
|
|
EBUS_EVENT_ID(child.m_entityId, UiElementBus, FindDescendantElements, predicate, result);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::CallOnDescendantElements(AZStd::function<void(const AZ::EntityId)> callFunction)
|
|
{
|
|
if (AreChildPointersValid())
|
|
{
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
callFunction(m_childEntityIdOrder[i].m_entityId);
|
|
|
|
GetChildElementComponent(i)->CallOnDescendantElements(callFunction);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto& child : m_childEntityIdOrder)
|
|
{
|
|
callFunction(child.m_entityId);
|
|
EBUS_EVENT_ID(child.m_entityId, UiElementBus, CallOnDescendantElements, callFunction);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::IsAncestor(AZ::EntityId id)
|
|
{
|
|
UiElementComponent* parentElementComponent = GetParentElementComponent();
|
|
while (parentElementComponent)
|
|
{
|
|
if (parentElementComponent->GetEntityId() == id)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
parentElementComponent = parentElementComponent->GetParentElementComponent();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::IsEnabled()
|
|
{
|
|
return m_isEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetIsEnabled(bool isEnabled)
|
|
{
|
|
if (isEnabled != m_isEnabled)
|
|
{
|
|
m_isEnabled = isEnabled;
|
|
|
|
// Tell any listeners that the enabled state has changed
|
|
EBUS_EVENT_ID(GetEntityId(), UiElementNotificationBus, OnUiElementEnabledChanged, m_isEnabled);
|
|
|
|
// If the ancestors are not enabled then changing the local flag has no effect on the effective
|
|
// enabled state.
|
|
bool areAncestorsEnabled = (m_parentElementComponent) ? m_parentElementComponent->GetAreElementAndAncestorsEnabled() : true;
|
|
if (areAncestorsEnabled)
|
|
{
|
|
// Tell any listeners that the effective enabled state has changed
|
|
EBUS_EVENT_ID(GetEntityId(), UiElementNotificationBus, OnUiElementAndAncestorsEnabledChanged, m_isEnabled);
|
|
|
|
DoRecursiveEnabledNotification(m_isEnabled);
|
|
}
|
|
|
|
// tell the canvas to invalidate the render graph
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::GetAreElementAndAncestorsEnabled()
|
|
{
|
|
if (!m_isEnabled)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_parentElementComponent)
|
|
{
|
|
return m_parentElementComponent->GetAreElementAndAncestorsEnabled();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::IsRenderEnabled()
|
|
{
|
|
return m_isRenderEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetIsRenderEnabled(bool isRenderEnabled)
|
|
{
|
|
m_isRenderEnabled = isRenderEnabled;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::GetIsVisible()
|
|
{
|
|
return m_isVisibleInEditor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetIsVisible(bool isVisible)
|
|
{
|
|
if (m_isVisibleInEditor != isVisible)
|
|
{
|
|
m_isVisibleInEditor = isVisible;
|
|
|
|
if (m_canvas)
|
|
{
|
|
// we have to regenerate the graph because different elements are now visible
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::GetIsSelectable()
|
|
{
|
|
return m_isSelectableInEditor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetIsSelectable(bool isSelectable)
|
|
{
|
|
m_isSelectableInEditor = isSelectable;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::GetIsSelected()
|
|
{
|
|
return m_isSelectedInEditor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetIsSelected(bool isSelected)
|
|
{
|
|
m_isSelectedInEditor = isSelected;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::GetIsExpanded()
|
|
{
|
|
return m_isExpandedInEditor;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetIsExpanded(bool isExpanded)
|
|
{
|
|
m_isExpandedInEditor = isExpanded;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::AreAllAncestorsVisible()
|
|
{
|
|
UiElementComponent* parentElementComponent = GetParentElementComponent();
|
|
while (parentElementComponent)
|
|
{
|
|
bool isParentVisible = true;
|
|
EBUS_EVENT_ID_RESULT(isParentVisible, parentElementComponent->GetEntityId(), UiEditorBus, GetIsVisible);
|
|
if (!isParentVisible)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Walk up the hierarchy.
|
|
parentElementComponent = parentElementComponent->GetParentElementComponent();
|
|
}
|
|
|
|
// there is no ancestor entity that is not visible
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::OnEntityActivated(const AZ::EntityId&)
|
|
{
|
|
// cache pointers to the optional render interface and render control interface to
|
|
// optimize calls rather than using ebus. Both of these buses only allow single handlers.
|
|
m_renderInterface = UiRenderBus::FindFirstHandler(GetEntityId());
|
|
m_renderControlInterface = UiRenderControlBus::FindFirstHandler(GetEntityId());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::OnEntityDeactivated(const AZ::EntityId&)
|
|
{
|
|
m_renderInterface = nullptr;
|
|
m_renderControlInterface = nullptr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::AddChild(AZ::Entity* child, AZ::Entity* insertBefore)
|
|
{
|
|
// debug check that this element is not already a child
|
|
AZ_Assert(FindChildByEntityId(child->GetId()) == nullptr, "Attempting to add a duplicate child");
|
|
|
|
UiElementComponent* childElementComponent = child->FindComponent<UiElementComponent>();
|
|
AZ_Assert(childElementComponent, "Attempting to add a child with no element component");
|
|
if (!childElementComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool wasInserted = false;
|
|
|
|
if (insertBefore)
|
|
{
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
for (int i = 0; i < numChildren; ++i)
|
|
{
|
|
if (m_childEntityIdOrder[i].m_entityId == insertBefore->GetId())
|
|
{
|
|
if (AreChildPointersValid()) // must test before m_childEntityIdOrder.insert
|
|
{
|
|
m_childElementComponents.insert(m_childElementComponents.begin() + i, childElementComponent);
|
|
}
|
|
|
|
m_childEntityIdOrder.insert(m_childEntityIdOrder.begin() + i, {child->GetId(), static_cast<AZ::u64>(i)});
|
|
|
|
ResetChildEntityIdSortOrders();
|
|
|
|
wasInserted = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// either insertBefore is null or it is not found, insert at end
|
|
if (!wasInserted)
|
|
{
|
|
if (AreChildPointersValid()) // must test before m_childEntityIdOrder.push_back
|
|
{
|
|
m_childElementComponents.push_back(childElementComponent);
|
|
}
|
|
m_childEntityIdOrder.push_back({child->GetId(), m_childEntityIdOrder.size()});
|
|
}
|
|
|
|
// Adding or removing child elements may require recomputing the
|
|
// transforms of all children
|
|
EBUS_EVENT_ID(GetCanvasEntityId(), UiLayoutManagerBus, MarkToRecomputeLayout, GetEntityId());
|
|
EBUS_EVENT_ID(GetCanvasEntityId(), UiLayoutManagerBus, MarkToRecomputeLayoutsAffectedByLayoutCellChange, GetEntityId(), false);
|
|
|
|
// It will always require recomputing the transform for the child just added
|
|
if (IsFullyInitialized())
|
|
{
|
|
GetTransform2dComponent()->SetRecomputeFlags(UiTransformInterface::Recompute::RectAndTransform);
|
|
}
|
|
|
|
// tell the canvas to invalidate the render graph
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::RemoveChild(AZ::Entity* child)
|
|
{
|
|
// check if the given entity is actually a child, if not then do nothing
|
|
AZ::EntityId childId = child->GetId();
|
|
auto iter = AZStd::find_if(m_childEntityIdOrder.begin(), m_childEntityIdOrder.end(),
|
|
[childId](ChildEntityIdOrderEntry& entry) { return entry.m_entityId == childId; });
|
|
|
|
if (iter != m_childEntityIdOrder.end())
|
|
{
|
|
// remove the child from m_childEntityIdOrder
|
|
m_childEntityIdOrder.erase(iter);
|
|
|
|
// update the sort indices to be contiguous
|
|
ResetChildEntityIdSortOrders();
|
|
|
|
UiElementComponent* elementComponent = child->FindComponent<UiElementComponent>();
|
|
AZ_Assert(elementComponent, "Child element has no UiElementComponent");
|
|
|
|
// Also erase from m_childElementComponents
|
|
stl::find_and_erase(m_childElementComponents, elementComponent);
|
|
|
|
// Clear child's parent
|
|
elementComponent->SetParentReferences(nullptr, nullptr);
|
|
|
|
// Adding or removing child elements may require recomputing the
|
|
// transforms of all children
|
|
EBUS_EVENT_ID(GetCanvasEntityId(), UiLayoutManagerBus, MarkToRecomputeLayout, GetEntityId());
|
|
EBUS_EVENT_ID(GetCanvasEntityId(), UiLayoutManagerBus, MarkToRecomputeLayoutsAffectedByLayoutCellChange, GetEntityId(), false);
|
|
|
|
// tell the canvas to invalidate the render graph
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::RemoveChild(AZ::EntityId child)
|
|
{
|
|
AZ::EntityId childId = child;
|
|
auto iter = AZStd::find_if(m_childEntityIdOrder.begin(), m_childEntityIdOrder.end(),
|
|
[childId](ChildEntityIdOrderEntry& entry) { return entry.m_entityId == childId; });
|
|
|
|
if (iter != m_childEntityIdOrder.end())
|
|
{
|
|
if (AreChildPointersValid())
|
|
{
|
|
auto childElementiter = AZStd::find_if(m_childElementComponents.begin(), m_childElementComponents.end(),
|
|
[childId](UiElementComponent* elementComponent) { return elementComponent->GetEntityId() == childId; });
|
|
|
|
UiElementComponent* elementComponent = childElementiter != m_childElementComponents.end() ? *childElementiter : nullptr;
|
|
AZ_Assert(elementComponent, "");
|
|
if (elementComponent)
|
|
{
|
|
stl::find_and_erase(m_childElementComponents, elementComponent);
|
|
|
|
// Clear child's parent
|
|
elementComponent->SetParentReferences(nullptr, nullptr);
|
|
}
|
|
}
|
|
|
|
// remove the child from m_childEntityIdOrder
|
|
m_childEntityIdOrder.erase(iter);
|
|
|
|
// update the sort indicies to be contiguous
|
|
ResetChildEntityIdSortOrders();
|
|
|
|
// Adding or removing child elements may require recomputing the
|
|
// transforms of all children
|
|
EBUS_EVENT_ID(GetCanvasEntityId(), UiLayoutManagerBus, MarkToRecomputeLayout, GetEntityId());
|
|
EBUS_EVENT_ID(GetCanvasEntityId(), UiLayoutManagerBus, MarkToRecomputeLayoutsAffectedByLayoutCellChange, GetEntityId(), false);
|
|
|
|
// tell the canvas to invalidate the render graph
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetCanvas(UiCanvasComponent* canvas, LyShine::ElementId elementId)
|
|
{
|
|
m_canvas = canvas;
|
|
m_elementId = elementId;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::FixupPostLoad(AZ::Entity* entity, UiCanvasComponent* canvas, AZ::Entity* parent, bool makeNewElementIds)
|
|
{
|
|
#ifdef AZ_DEBUG_BUILD
|
|
// check that the m_childEntityIdOrder is ordered such that the m_sortIndex fields are in order and contiguous
|
|
{
|
|
size_t numChildren = m_childEntityIdOrder.size();
|
|
for (size_t index = 0; index < numChildren; ++index)
|
|
{
|
|
if (m_childEntityIdOrder[index].m_sortIndex != index)
|
|
{
|
|
AZ_Assert(false, "FixupPostLoad: m_childEntityIdOrder bad sort index. This should never happen.");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (makeNewElementIds)
|
|
{
|
|
m_elementId = canvas->GenerateId();
|
|
}
|
|
|
|
m_canvas = canvas;
|
|
|
|
if (parent)
|
|
{
|
|
UiElementComponent* parentElementComponent = parent->FindComponent<UiElementComponent>();
|
|
AZ_Assert(parentElementComponent, "Parent element has no UiElementComponent");
|
|
SetParentReferences(parent, parentElementComponent);
|
|
}
|
|
else
|
|
{
|
|
SetParentReferences(nullptr, nullptr);
|
|
}
|
|
|
|
ChildEntityIdOrderArray missingChildren;
|
|
|
|
for (auto& child : m_childEntityIdOrder)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, child.m_entityId);
|
|
if (!childEntity)
|
|
{
|
|
// with slices it is possible for users to get themselves into situations where a child no
|
|
// longer exists, we should report an error in this case rather than asserting
|
|
AZ_Error("UI", false, "Child element with Entity ID %llu no longer exists. Data will be lost.", child);
|
|
// This case could happen if a slice asset has been deleted. We should try to continue and load the
|
|
// canvas with errors.
|
|
missingChildren.push_back(child);
|
|
continue;
|
|
}
|
|
|
|
UiElementComponent* elementComponent = childEntity->FindComponent<UiElementComponent>();
|
|
if (!elementComponent)
|
|
{
|
|
// with slices it is possible for users to get themselves into situations where a child no
|
|
// longer has an element component. In this case report an error and fail to load the data but do not
|
|
// crash.
|
|
AZ_Error("UI", false, "Child element with Entity ID %llu no longer has a UiElementComponent. Data cannot be loaded.", child);
|
|
return false;
|
|
}
|
|
|
|
bool success = elementComponent->FixupPostLoad(childEntity, canvas, entity, makeNewElementIds);
|
|
if (!success)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If there were any missing children remove them from the m_childEntityIdOrder list
|
|
// This is recovery code for the case that a slice asset that we were using has been removed.
|
|
for (auto child : missingChildren)
|
|
{
|
|
stl::find_and_erase(m_childEntityIdOrder, child);
|
|
}
|
|
|
|
// Initialize the m_childElementComponents array that is used for performance optimization
|
|
m_childElementComponents.clear();
|
|
for (auto child : m_childEntityIdOrder)
|
|
{
|
|
AZ::Entity* childEntity = nullptr;
|
|
EBUS_EVENT_RESULT(childEntity, AZ::ComponentApplicationBus, FindEntity, child.m_entityId);
|
|
AZ_Assert(childEntity, "Child element not found");
|
|
UiElementComponent* childElementComponent = childEntity->FindComponent<UiElementComponent>();
|
|
AZ_Assert(childElementComponent, "Child element has no UiElementComponent");
|
|
m_childElementComponents.push_back(childElementComponent);
|
|
}
|
|
|
|
// Tell any listeners that the canvas entity ID for the element is now set, this allows other components to
|
|
// listen for messages from the canvas
|
|
AZ::EntityId parentEntityId = (parent) ? parent->GetId() : AZ::EntityId();
|
|
EBUS_EVENT_ID(GetEntityId(), UiElementNotificationBus, OnUiElementFixup, canvas->GetEntityId(), parentEntityId);
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
AZ::EntityId UiElementComponent::GetSliceEntityParentId()
|
|
{
|
|
return GetParentEntityId();
|
|
}
|
|
|
|
AZStd::vector<AZ::EntityId> UiElementComponent::GetSliceEntityChildren()
|
|
{
|
|
return GetChildEntityIds();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PUBLIC STATIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void UiElementComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
|
|
if (serializeContext)
|
|
{
|
|
serializeContext->Class<ChildEntityIdOrderEntry>()
|
|
// Persistent IDs for this are simply the entity id
|
|
->PersistentId([](const void* instance) -> AZ::u64
|
|
{
|
|
const ChildEntityIdOrderEntry* entry = reinterpret_cast<const ChildEntityIdOrderEntry*>(instance);
|
|
return static_cast<AZ::u64>(entry->m_entityId);
|
|
})
|
|
->Version(1)
|
|
->Field("ChildEntityId", &ChildEntityIdOrderEntry::m_entityId)
|
|
->Field("SortIndex", &ChildEntityIdOrderEntry::m_sortIndex);
|
|
|
|
serializeContext->Class<UiElementComponent, AZ::Component>()
|
|
->Version(3, &VersionConverter)
|
|
->EventHandler<ChildOrderSerializationEvents>()
|
|
->Field("Id", &UiElementComponent::m_elementId)
|
|
->Field("IsEnabled", &UiElementComponent::m_isEnabled)
|
|
->Field("IsVisibleInEditor", &UiElementComponent::m_isVisibleInEditor)
|
|
->Field("IsSelectableInEditor", &UiElementComponent::m_isSelectableInEditor)
|
|
->Field("IsSelectedInEditor", &UiElementComponent::m_isSelectedInEditor)
|
|
->Field("IsExpandedInEditor", &UiElementComponent::m_isExpandedInEditor)
|
|
->Field("ChildEntityIdOrder", &UiElementComponent::m_childEntityIdOrder);
|
|
|
|
AZ::EditContext* ec = serializeContext->GetEditContext();
|
|
if (ec)
|
|
{
|
|
auto editInfo = ec->Class<UiElementComponent>("Element", "Adds UI Element behavior to an entity");
|
|
|
|
editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiElement.png")
|
|
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiElement.png")
|
|
->Attribute(AZ::Edit::Attributes::AddableByUser, false) // Cannot be added or removed by user
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
|
|
|
editInfo->DataElement("String", &UiElementComponent::m_elementId, "Id",
|
|
"This read-only ID is used to reference the element from FlowGraph")
|
|
->Attribute(AZ::Edit::Attributes::ReadOnly, true)
|
|
->Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushable);
|
|
|
|
editInfo->DataElement(0, &UiElementComponent::m_isEnabled, "Start enabled",
|
|
"Determines whether the element is enabled upon creation.\n"
|
|
"If an element is not enabled, neither it nor any of its children are drawn or interactive.");
|
|
|
|
// These are not visible in the PropertyGrid since they are managed through the Hierarchy Pane
|
|
// We do want to be able to push them to a slice though.
|
|
editInfo->DataElement(0, &UiElementComponent::m_isVisibleInEditor, "IsVisibleInEditor", "")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide);
|
|
editInfo->DataElement(0, &UiElementComponent::m_isSelectableInEditor, "IsSelectableInEditor", "")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide);
|
|
editInfo->DataElement(0, &UiElementComponent::m_isExpandedInEditor, "IsExpandedInEditor", "")
|
|
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide);
|
|
}
|
|
}
|
|
|
|
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
|
|
if (behaviorContext)
|
|
{
|
|
behaviorContext->EBus<UiElementBus>("UiElementBus")
|
|
->Event("GetName", &UiElementBus::Events::GetName)
|
|
->Event("GetCanvas", &UiElementBus::Events::GetCanvasEntityId)
|
|
->Event("GetParent", &UiElementBus::Events::GetParentEntityId)
|
|
->Event("GetNumChildElements", &UiElementBus::Events::GetNumChildElements)
|
|
->Event("GetChild", &UiElementBus::Events::GetChildEntityId)
|
|
->Event("GetIndexOfChildByEntityId", &UiElementBus::Events::GetIndexOfChildByEntityId)
|
|
->Event("GetChildren", &UiElementBus::Events::GetChildEntityIds)
|
|
->Event("DestroyElement", &UiElementBus::Events::DestroyElementOnFrameEnd)
|
|
->Event("Reparent", &UiElementBus::Events::ReparentByEntityId)
|
|
->Event("FindChildByName", &UiElementBus::Events::FindChildEntityIdByName)
|
|
->Event("FindDescendantByName", &UiElementBus::Events::FindDescendantEntityIdByName)
|
|
->Event("IsAncestor", &UiElementBus::Events::IsAncestor)
|
|
->Event("IsEnabled", &UiElementBus::Events::IsEnabled)
|
|
->Event("SetIsEnabled", &UiElementBus::Events::SetIsEnabled);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::Initialize()
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::MoveEntityAndDescendantsToListAndReplaceWithEntityId(AZ::SerializeContext& context,
|
|
AZ::SerializeContext::DataElementNode& elementNode,
|
|
int index,
|
|
AZStd::vector<AZ::SerializeContext::DataElementNode>& entities)
|
|
{
|
|
// Find the UiElementComponent on this entity
|
|
AZ::SerializeContext::DataElementNode* elementComponentNode =
|
|
LyShine::FindComponentNode(elementNode, UiElementComponent::TYPEINFO_Uuid());
|
|
if (!elementComponentNode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We must process the children first so that when we make a copy of this entity to the entities list
|
|
// it will already have had its child entities replaced with entity IDs
|
|
|
|
// find the m_children field
|
|
int childrenIndex = elementComponentNode->FindElement(AZ_CRC("Children", 0xa197b1ba));
|
|
if (childrenIndex == -1)
|
|
{
|
|
return false;
|
|
}
|
|
AZ::SerializeContext::DataElementNode& childrenNode = elementComponentNode->GetSubElement(childrenIndex);
|
|
|
|
// Create the child entities member (which is a generic vector)
|
|
AZ::SerializeContext::ClassData* classData = AZ::SerializeGenericTypeInfo<ChildEntityIdOrderArray>::GetGenericInfo()->GetClassData();
|
|
int newChildrenIndex = elementComponentNode->AddElement(context, "ChildEntityIdOrder", *classData);
|
|
if (newChildrenIndex == -1)
|
|
{
|
|
return false;
|
|
}
|
|
AZ::SerializeContext::DataElementNode& newChildrenNode = elementComponentNode->GetSubElement(newChildrenIndex);
|
|
|
|
// iterate through children and recursively call this function
|
|
int numChildren = childrenNode.GetNumSubElements();
|
|
for (int childIndex = 0; childIndex < numChildren; ++childIndex)
|
|
{
|
|
AZ::SerializeContext::DataElementNode& childElementNode = childrenNode.GetSubElement(childIndex);
|
|
MoveEntityAndDescendantsToListAndReplaceWithEntityId(context, childElementNode, childIndex, entities);
|
|
|
|
newChildrenNode.AddElement(childElementNode);
|
|
}
|
|
|
|
// delete the original "Children" node, we have replaced it with the "ChildEntityIdOrder" node
|
|
elementComponentNode->RemoveElement(childrenIndex);
|
|
|
|
// the children list has now been processed so it will now just contain entity IDs
|
|
// Now copy this node (elementNode) to the list we are building and then replace it
|
|
// with an Entity ID node
|
|
|
|
// copy this node to the list
|
|
entities.push_back(elementNode);
|
|
|
|
// Remember the name of this node (it could be "element" or "RootElement" for example)
|
|
AZStd::string elementFieldName = elementNode.GetNameString();
|
|
|
|
// Find the EntityId node within this entity
|
|
int entityIdIndex = elementNode.FindElement(AZ_CRC("Id", 0xbf396750));
|
|
if (entityIdIndex == -1)
|
|
{
|
|
return false;
|
|
}
|
|
AZ::SerializeContext::DataElementNode& elementIdNode = elementNode.GetSubElement(entityIdIndex);
|
|
|
|
// Find the sub node of the EntityID that actually stores the u64 and make a copy of it
|
|
int u64Index = elementIdNode.FindElement(AZ_CRC("id", 0xbf396750));
|
|
if (u64Index == -1)
|
|
{
|
|
return false;
|
|
}
|
|
AZ::SerializeContext::DataElementNode u64Node = elementIdNode.GetSubElement(u64Index);
|
|
|
|
// -1 indicates this is the root element reference
|
|
if (index == -1)
|
|
{
|
|
// Convert this node (which was an entire Entity) into just an EntityId, keeping the same
|
|
// node name as it had
|
|
elementNode.Convert<AZ::EntityId>(context, elementFieldName.c_str());
|
|
|
|
// copy in the subNode that stores the actual u64 (that we saved a copy of above)
|
|
elementNode.AddElement(u64Node);
|
|
}
|
|
else
|
|
{
|
|
// Convert this node (which was an entire Entity) into just an ChildEntityIdOrderEntry, keeping the same
|
|
// node name as it had
|
|
elementNode.Convert<ChildEntityIdOrderEntry>(context, elementFieldName.c_str());
|
|
|
|
// add sub element from the entity Id
|
|
int childOrderEntryEntityIdIndex = elementNode.AddElement<AZ::EntityId>(context, "ChildEntityId");
|
|
AZ::SerializeContext::DataElementNode& childOrderEntryEntityIdElementNode = elementNode.GetSubElement(childOrderEntryEntityIdIndex);
|
|
|
|
// copy in the subNode that stores the actual u64 (that we saved a copy of above)
|
|
childOrderEntryEntityIdElementNode.AddElement(u64Node);
|
|
|
|
AZ::u64 sortIndex = static_cast<AZ::u64>(index);
|
|
elementNode.AddElementWithData<AZ::u64>(context, "SortIndex", sortIndex);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PROTECTED MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::Activate()
|
|
{
|
|
UiElementBus::Handler::BusConnect(m_entity->GetId());
|
|
UiEditorBus::Handler::BusConnect(m_entity->GetId());
|
|
AZ::SliceEntityHierarchyRequestBus::Handler::BusConnect(m_entity->GetId());
|
|
AZ::EntityBus::Handler::BusConnect(m_entity->GetId());
|
|
|
|
// Once added the transform component is never removed
|
|
if (!m_transformComponent)
|
|
{
|
|
m_transformComponent = GetEntity()->FindComponent<UiTransform2dComponent>();
|
|
}
|
|
|
|
// tell the canvas to invalidate the render graph
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::Deactivate()
|
|
{
|
|
UiElementBus::Handler::BusDisconnect();
|
|
UiEditorBus::Handler::BusDisconnect();
|
|
AZ::SliceEntityHierarchyRequestBus::Handler::BusDisconnect();
|
|
AZ::EntityBus::Handler::BusDisconnect();
|
|
|
|
// tell the canvas to invalidate the render graph
|
|
if (m_canvas)
|
|
{
|
|
m_canvas->MarkRenderGraphDirty();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::DoRecursiveEnabledNotification(bool newIsEnabledValue)
|
|
{
|
|
for (UiElementComponent* child : m_childElementComponents)
|
|
{
|
|
// if this child element is disabled then the enabled state of the ancestors makes no difference
|
|
// but if it is enabled then its effective enabled state is controlled by its ancestors
|
|
if (child->m_isEnabled)
|
|
{
|
|
EBUS_EVENT_ID(child->GetEntityId(), UiElementNotificationBus, OnUiElementAndAncestorsEnabledChanged, newIsEnabledValue);
|
|
child->DoRecursiveEnabledNotification(newIsEnabledValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::EmitNotInitializedWarning() const
|
|
{
|
|
AZ_Warning("UI", false, "UiElementComponent used before fully initialized, possibly on activate before FixupPostLoad was called on this element")
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::SetParentReferences(AZ::Entity* parent, UiElementComponent* parentElementComponent)
|
|
{
|
|
m_parent = parent;
|
|
m_parentId = (parent) ? parent->GetId() : AZ::EntityId();
|
|
m_parentElementComponent = parentElementComponent;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::OnPatchEnd(const AZ::DataPatchNodeInfo& patchInfo)
|
|
{
|
|
// We want to check the data patch for any patching of the "Children" element. The m_children element no
|
|
// longer exists so we want to make the equivalent changes to the m_childEntityIdOrder element.
|
|
|
|
// The relevant patch addresses can be either
|
|
// a) a change of an element in the container
|
|
// b) a removal of an element in the container (these are always higher indices than the changes)
|
|
// c) an addition of an element in the container (these are not always in ascending order, it is an unordered map)
|
|
// (these are always higher indices than the changes)
|
|
//
|
|
// For a given patch there will never be both addition and removals.
|
|
//
|
|
// For b and c the patch address (in childPatchLookup) will be patchinfo.address + "Children".
|
|
// We could find all of those through one call to "find" on childPatchLookup with that address.
|
|
// However, for the "a" case the address (in childPatchLookup) will have an additional element on
|
|
// the end - since it is the "Id" field within the EntityId that is being patched.
|
|
// So we have to iterate through childPatchLookup anyway, so we do that for all cases.
|
|
|
|
using EntityIndexPair = AZStd::pair<AZ::u64, AZ::EntityId>;
|
|
using EntityIndexPairList = AZStd::vector<EntityIndexPair>;
|
|
|
|
EntityIndexPairList elementsChanged;
|
|
EntityIndexPairList elementsAdded;
|
|
AZStd::vector<AZ::u64> elementsRemoved;
|
|
bool oldChildrenDataPatchFound = false;
|
|
|
|
const AZ::DataPatch::AddressType& address = patchInfo.address;
|
|
const AZ::DataPatch::PatchMap& patch = patchInfo.patch;
|
|
const AZ::DataPatch::ChildPatchMap& childPatchLookup = patchInfo.childPatchLookup;
|
|
|
|
// Build the address of the "Children" element within this UiElementComponent
|
|
AZ::DataPatch::AddressType childrenAddress = address;
|
|
childrenAddress.push_back(AZ_CRC("Children", 0xa197b1ba));
|
|
|
|
// Get the serialize context for use in the LoadObjectFromStreamInPlace calls
|
|
AZ::SerializeContext* serializeContext = nullptr;
|
|
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
|
|
|
// childPatchLookup contains all addresses in the patch that are within the UiElementComponent so
|
|
// it is slightly faster to iterate over that than over "patch" directly.
|
|
for (auto& childPatchPair : childPatchLookup)
|
|
{
|
|
const AZ::DataPatch::AddressType& lookupAddress = childPatchPair.first;
|
|
|
|
if (lookupAddress == childrenAddress)
|
|
{
|
|
// The address matches the "Children" container exactly, so get childPatches which will contain
|
|
// all the additions and removals to the container.
|
|
const AZStd::vector<AZ::DataPatch::AddressType>& childPatches = childPatchPair.second;
|
|
|
|
for (auto& childPatchAddress : childPatches)
|
|
{
|
|
auto foundPatchIt = patch.find(childPatchAddress);
|
|
|
|
if (foundPatchIt == patch.end())
|
|
{
|
|
// this should never happen, ignore it if it does
|
|
continue;
|
|
}
|
|
|
|
// the last part of the address is the index in the m_children array
|
|
AZ::u64 index = childPatchAddress.back().GetAddressElement();
|
|
|
|
if (foundPatchIt->second.empty())
|
|
{
|
|
// this is removal of element (actual patch is empty)
|
|
oldChildrenDataPatchFound = true;
|
|
elementsRemoved.push_back(index);
|
|
}
|
|
else
|
|
{
|
|
// This is an addition
|
|
|
|
// get the EntityId out of the patch value
|
|
AZ::EntityId entityId;
|
|
bool entityIdLoaded = false;
|
|
|
|
// If the patch originated in a Legacy DataPatch then we must first load the EntityId from the legacy stream
|
|
if (foundPatchIt->second.type() == azrtti_typeid<AZ::DataPatch::LegacyStreamWrapper>())
|
|
{
|
|
const AZ::DataPatch::LegacyStreamWrapper* wrapper = AZStd::any_cast<AZ::DataPatch::LegacyStreamWrapper>(&foundPatchIt->second);
|
|
|
|
if (wrapper)
|
|
{
|
|
AZ::IO::MemoryStream stream(wrapper->m_stream.data(), wrapper->m_stream.size());
|
|
entityIdLoaded = AZ::Utils::LoadObjectFromStreamInPlace<AZ::EntityId>(stream, entityId, serializeContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Otherwise we can acquire the EntityId from the patch directly
|
|
const AZ::EntityId* entityIdPtr = AZStd::any_cast<AZ::EntityId>(&foundPatchIt->second);
|
|
|
|
if (entityIdPtr)
|
|
{
|
|
entityId = *entityIdPtr;
|
|
entityIdLoaded = true;
|
|
}
|
|
}
|
|
|
|
if (entityIdLoaded)
|
|
{
|
|
oldChildrenDataPatchFound = true;
|
|
elementsAdded.push_back({index, entityId});
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("UI", false, "UiElement::OnPatchEnd: Failed to load a child entity Id from DataPatch");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (lookupAddress.size() == childrenAddress.size() + 1)
|
|
{
|
|
// the lookupAddress is the same length as the "Children" address plus an index
|
|
// check if the address is childrenAddress plus an extra element
|
|
bool match = true;
|
|
for (int i = static_cast<int>(childrenAddress.size() - 1); i >= 0; --i)
|
|
{
|
|
if (lookupAddress[i] != childrenAddress[i])
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!match)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// childPatches will be any patches to this one element in the children array (should only ever be one element in the map)
|
|
const AZStd::vector<AZ::DataPatch::AddressType>& childPatches = childPatchPair.second;
|
|
|
|
for (auto& childPatchAddress : childPatches)
|
|
{
|
|
auto foundPatchIt = patch.find(childPatchAddress);
|
|
|
|
if (foundPatchIt == patch.end())
|
|
{
|
|
// this should never happen, ignore it if it does
|
|
continue;
|
|
}
|
|
|
|
if (foundPatchIt->second.empty())
|
|
{
|
|
// this is removal of element (actual patch is empty). Should never occur in this path. Ignore.
|
|
continue;
|
|
}
|
|
|
|
// This should be the u64 "Id" element of the EntityId, if not ignore.
|
|
if (childPatchAddress.back().GetAddressElement() == AZ_CRC("Id", 0xbf396750))
|
|
{
|
|
// the second to last part of the address is the index in the m_children array
|
|
AZ::u64 index = childPatchAddress[childPatchAddress.size() - 2].GetAddressElement();
|
|
|
|
// extract the u64 from the patch value
|
|
AZ::u64 id = 0;
|
|
bool idLoaded = false;
|
|
|
|
// If the patch originated in a Legacy DataPatch then we must first load the u64 from the legacy stream
|
|
if (foundPatchIt->second.type() == azrtti_typeid<AZ::DataPatch::LegacyStreamWrapper>())
|
|
{
|
|
const AZ::DataPatch::LegacyStreamWrapper* wrapper = AZStd::any_cast<AZ::DataPatch::LegacyStreamWrapper>(&foundPatchIt->second);
|
|
|
|
if (wrapper)
|
|
{
|
|
AZ::IO::MemoryStream stream(wrapper->m_stream.data(), wrapper->m_stream.size());
|
|
|
|
idLoaded = AZ::Utils::LoadObjectFromStreamInPlace<AZ::u64>(stream, id, serializeContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Otherwise we can acquire the EntityId from the patch directly
|
|
const AZ::u64* idPtr = AZStd::any_cast<AZ::u64>(&foundPatchIt->second);
|
|
|
|
if (idPtr)
|
|
{
|
|
id = *idPtr;
|
|
idLoaded = true;
|
|
}
|
|
}
|
|
|
|
if (idLoaded)
|
|
{
|
|
AZ::EntityId entityId(id);
|
|
oldChildrenDataPatchFound = true;
|
|
elementsChanged.push_back({index, entityId});
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("UI", false, "UiElement::OnPatchEnd: Failed to load a child entity Id from DataPatch");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if patch data for the old "Children" container was found then apply it to the new m_childEntityIdOrder vector
|
|
if (oldChildrenDataPatchFound)
|
|
{
|
|
if (elementsAdded.size() > 0 && elementsRemoved.size() > 0)
|
|
{
|
|
AZ_Error("UI", false, "OnPatchEnd: can't add and remove in the same patch");
|
|
}
|
|
|
|
// removing elements always removes from the end. So we just need to resize to the lowest index
|
|
for (AZ::u64 index : elementsRemoved)
|
|
{
|
|
if (index < m_childEntityIdOrder.size())
|
|
{
|
|
m_childEntityIdOrder.resize(index);
|
|
}
|
|
}
|
|
|
|
for (auto& elementChanged : elementsChanged)
|
|
{
|
|
AZ::u64 index = elementChanged.first;
|
|
if (index < m_childEntityIdOrder.size())
|
|
{
|
|
m_childEntityIdOrder[index].m_entityId = elementChanged.second;
|
|
}
|
|
else
|
|
{
|
|
// index is off the end of m_childEntityIdOrder, this can happen because
|
|
// elements could be been removed from the slice. But since this override has changed
|
|
// the entityId we do not want to remove it. So add at end.
|
|
m_childEntityIdOrder.push_back({elementChanged.second, m_childEntityIdOrder.size()});
|
|
}
|
|
}
|
|
|
|
// sort the added elements by index
|
|
AZStd::sort(elementsAdded.begin(), elementsAdded.end());
|
|
for (auto& elementAdded : elementsAdded)
|
|
{
|
|
// elements could have been added or removed in the slice so we don't require that there must be an element 3
|
|
// to add element 4, if not we just add it at the end.
|
|
m_childEntityIdOrder.push_back({elementAdded.second, m_childEntityIdOrder.size()});
|
|
}
|
|
}
|
|
|
|
// regardless of whether the old m_children was in the patch we always sort m_childEntityIdOrder and reassign sort indices after
|
|
// patching to maintain a consecutive set of sort indices
|
|
|
|
// This will sort all the entity order entries by sort index (primary) and entity id (secondary) which should never result in any collisions
|
|
// This is used since slice data patching may create duplicate entries for the same sort index, missing indices and the like.
|
|
// It should never result in multiple entity id entries since the serialization of this data uses a persistent id which is the entity id
|
|
int numChildren = static_cast<int>(m_childEntityIdOrder.size());
|
|
if (numChildren > 0)
|
|
{
|
|
AZStd::sort(m_childEntityIdOrder.begin(), m_childEntityIdOrder.end());
|
|
ResetChildEntityIdSortOrders();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::ResetChildEntityIdSortOrders()
|
|
{
|
|
// Set the sortIndex on each child to match the order in the vector
|
|
for (AZ::u64 childIndex = 0; childIndex < m_childEntityIdOrder.size(); ++childIndex)
|
|
{
|
|
m_childEntityIdOrder[childIndex].m_sortIndex = childIndex;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::PrepareElementForDestroy()
|
|
{
|
|
// destroy child elements, this is complicated by the fact that the child elements
|
|
// will attempt to remove themselves from the m_childEntityIdOrder list in their DestroyElement method.
|
|
// But, if the entities are not initialized yet the child parent pointer will be null.
|
|
// So the child may or may not remove itself from the list.
|
|
// So make a local copy of the list and iterate on that
|
|
if (AreChildPointersValid())
|
|
{
|
|
auto childElementComponents = m_childElementComponents;
|
|
for (auto child : childElementComponents)
|
|
{
|
|
// destroy the child
|
|
child->DestroyElement();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto children = m_childEntityIdOrder; // need a copy
|
|
for (auto& child : children)
|
|
{
|
|
// destroy the child
|
|
UiElementBus::Event(child.m_entityId, &UiElementBus::Events::DestroyElement);
|
|
}
|
|
}
|
|
|
|
// remove this element from parent
|
|
if (m_parent)
|
|
{
|
|
GetParentElementComponent()->RemoveChild(GetEntity());
|
|
}
|
|
|
|
// Notify listeners that the element is being destroyed
|
|
UiElementNotificationBus::Event(GetEntityId(), &UiElementNotificationBus::Events::OnUiElementBeingDestroyed);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// PRIVATE STATIC MEMBER FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
bool UiElementComponent::VersionConverter(AZ::SerializeContext& context,
|
|
AZ::SerializeContext::DataElementNode& classElement)
|
|
{
|
|
// conversion from version 1 to 2:
|
|
if (classElement.GetVersion() < 2)
|
|
{
|
|
// No need to actually convert anything because the CanvasFileObject takes care of it
|
|
// But it makes sense to bump the version number because m_children is now a container
|
|
// of EntityId rather than Entity*
|
|
}
|
|
|
|
// conversion from version 2 to 3:
|
|
// m_children replaced with m_childEntityIdOrder
|
|
// NOTE: We do not go through here if version is 1 since m_children will be an array of Entity*
|
|
// rather than EntityId. That complex conversion is handled in the recursive function
|
|
// MoveEntityAndDescendantsToListAndReplaceWithEntityId
|
|
if (classElement.GetVersion() == 2)
|
|
{
|
|
// Version 3 added the persistent member m_childEntityIdOrder with replaces m_children
|
|
// Find the "Children" element that we will be replacing.
|
|
int childrenIndex = classElement.FindElement(AZ_CRC("Children"));
|
|
if (childrenIndex != -1)
|
|
{
|
|
AZ::SerializeContext::DataElementNode& childrenElementNode = classElement.GetSubElement(childrenIndex);
|
|
|
|
// add the new "ChildEntityIdOrder" element, this is a container
|
|
int childOrderIndex = classElement.AddElement<ChildEntityIdOrderArray>(context, "ChildEntityIdOrder");
|
|
AZ::SerializeContext::DataElementNode& childOrderElementNode = classElement.GetSubElement(childOrderIndex);
|
|
|
|
int numChildren = childrenElementNode.GetNumSubElements();
|
|
|
|
// for each EntityId in the Children container create a ChildEntityIdOrderEntry in the ChildEntityIdOrder container
|
|
for (int childIndex = 0; childIndex < numChildren; ++childIndex)
|
|
{
|
|
AZ::SerializeContext::DataElementNode& childElementNode = childrenElementNode.GetSubElement(childIndex);
|
|
|
|
// add the entry in the container (or type ChildEntityIdOrderEntry which is a struct of EntityId and u64)
|
|
int childOrderEntryIndex = childOrderElementNode.AddElement<ChildEntityIdOrderEntry>(context, "element");
|
|
AZ::SerializeContext::DataElementNode& childOrderEntryElementNode = childOrderElementNode.GetSubElement(childOrderEntryIndex);
|
|
|
|
// copy the EntityId node from the Children container and change its name
|
|
int childOrderEntryEntityIdIndex = childOrderEntryElementNode.AddElement(childElementNode);
|
|
AZ::SerializeContext::DataElementNode& childOrderEntryEntityIdElementNode = childOrderEntryElementNode.GetSubElement(childOrderEntryEntityIdIndex);
|
|
childOrderEntryEntityIdElementNode.SetName("ChildEntityId");
|
|
|
|
// add the the sort index - which is just the position in the container when we are converting old data.
|
|
AZ::u64 sortIndex = childIndex;
|
|
childOrderEntryElementNode.AddElementWithData(context, "SortIndex", sortIndex);
|
|
}
|
|
|
|
// remove the old m_children persistent member
|
|
classElement.RemoveElementByName(AZ_CRC("Children"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void UiElementComponent::DestroyElementEntity(AZ::EntityId entityId)
|
|
{
|
|
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
|
|
AzFramework::EntityIdContextQueryBus::EventResult(contextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
|
|
|
|
UiEntityContextRequestBus::Event(contextId, &UiEntityContextRequestBus::Events::DestroyUiEntity, entityId);
|
|
}
|