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.
o3de/Gems/LyShine/Code/Editor/Animation/UiAnimViewAnimNode.cpp

1684 lines
57 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "UiCanvasEditor_precompiled.h"
#include "UiEditorAnimationBus.h"
#include "UiEditorDLLBus.h"
#include "UiAnimViewAnimNode.h"
#include "UiAnimViewTrack.h"
#include "UiAnimViewSequence.h"
#include "UiAnimViewUndo.h"
#include "UiAnimViewNodeFactories.h"
#include "AnimationContext.h"
#include "UiAnimViewSequenceManager.h"
#include "Objects/EntityObject.h"
#include "ViewManager.h"
#include "RenderViewport.h"
#include "Clipboard.h"
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/Utils.h>
#include <LyShine/Bus/UiAnimationBus.h>
bool CUiAnimViewAnimNode::s_isForcingAnimationBecausePropertyChanged = false;
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNodeBundle::AppendAnimNode(CUiAnimViewAnimNode* pNode)
{
stl::push_back_unique(m_animNodes, pNode);
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNodeBundle::AppendAnimNodeBundle(const CUiAnimViewAnimNodeBundle& bundle)
{
for (auto iter = bundle.m_animNodes.begin(); iter != bundle.m_animNodes.end(); ++iter)
{
AppendAnimNode(*iter);
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNodeBundle::ExpandAll(bool bAlsoExpandParentNodes)
{
std::set<CUiAnimViewNode*> nodesToExpand;
std::copy(m_animNodes.begin(), m_animNodes.end(), std::inserter(nodesToExpand, nodesToExpand.end()));
if (bAlsoExpandParentNodes)
{
for (auto iter = nodesToExpand.begin(); iter != nodesToExpand.end(); ++iter)
{
CUiAnimViewNode* pNode = *iter;
for (CUiAnimViewNode* pParent = pNode->GetParentNode(); pParent; pParent = pParent->GetParentNode())
{
nodesToExpand.insert(pParent);
}
}
}
for (auto iter = nodesToExpand.begin(); iter != nodesToExpand.end(); ++iter)
{
(*iter)->SetExpanded(true);
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNodeBundle::CollapseAll()
{
for (auto iter = m_animNodes.begin(); iter != m_animNodes.end(); ++iter)
{
(*iter)->SetExpanded(false);
}
}
//////////////////////////////////////////////////////////////////////////
const bool CUiAnimViewAnimNodeBundle::DoesContain(const CUiAnimViewNode* pTargetNode)
{
return stl::find(m_animNodes, pTargetNode);
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNodeBundle::Clear()
{
m_animNodes.clear();
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNode::CUiAnimViewAnimNode(IUiAnimSequence* pSequence, IUiAnimNode* pAnimNode, CUiAnimViewNode* pParentNode)
: CUiAnimViewNode(pParentNode)
, m_pAnimSequence(pSequence)
, m_pAnimNode(pAnimNode)
, m_pNodeUiAnimator(nullptr)
{
if (pAnimNode)
{
// Search for child nodes
const int nodeCount = pSequence->GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
IUiAnimNode* pNode = pSequence->GetNode(i);
IUiAnimNode* pNodeParentNode = pNode->GetParent();
// If our node is the parent, then the current node is a child of it
if (pAnimNode == pNodeParentNode)
{
CUiAnimViewAnimNodeFactory animNodeFactory;
CUiAnimViewAnimNode* pNewUiAVAnimNode = animNodeFactory.BuildAnimNode(pSequence, pNode, this);
m_childNodes.push_back(std::unique_ptr<CUiAnimViewNode>(pNewUiAVAnimNode));
}
}
// Search for tracks
const int trackCount = pAnimNode->GetTrackCount();
for (int i = 0; i < trackCount; ++i)
{
IUiAnimTrack* pTrack = pAnimNode->GetTrackByIndex(i);
CUiAnimViewTrackFactory trackFactory;
CUiAnimViewTrack* pNewUiAVTrack = trackFactory.BuildTrack(pTrack, this, this);
m_childNodes.push_back(std::unique_ptr<CUiAnimViewNode>(pNewUiAVTrack));
}
// Set owner to update entity UI Animation system entity IDs and remove it again
EBUS_EVENT_ID_RESULT(m_nodeEntityId, m_pAnimNode.get(), UiAnimNodeBus, GetAzEntityId);
m_pAnimNode->SetNodeOwner(nullptr);
}
SortNodes();
m_bExpanded = IsGroupNode();
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::UiElementPropertyChanged()
{
// we can detect which properties changed by comparing with a cached copy
// then we need to tell the AnimNode what changed. This can add new tracks
// and record new key values
s_isForcingAnimationBecausePropertyChanged = true;
bool valueChanged = false;
const float time = GetSequence()->GetTime();
if (m_nodeEntityId.IsValid() && !m_azEntityDataCache.empty())
{
AZ::Entity* pNodeEntity = nullptr;
EBUS_EVENT_RESULT(pNodeEntity, AZ::ComponentApplicationBus, FindEntity, m_nodeEntityId);
// Check that the entity referenced by this AnimNode exists. There is a possiblity that
// it has been deleted (in which case this anim node is drawn in red).
if (pNodeEntity)
{
// The entity still exists, compare with cached data to see what changed
AZ::SerializeContext* context = nullptr;
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialization context found");
AZ::IO::ByteContainerStream<const AZStd::string> charStream(&m_azEntityDataCache);
AZ::Entity* oldEntity = AZ::Utils::LoadObjectFromStream<AZ::Entity>(charStream);
const AZ::Entity::ComponentArrayType& oldComponents = oldEntity->GetComponents();
const AZ::Entity::ComponentArrayType& newComponents = pNodeEntity->GetComponents();
// if the number of components has changed then a component has just been added of removed
// we do not record such changes
if (oldComponents.size() == newComponents.size())
{
for (AZ::u32 componentIndex = 0; componentIndex < oldComponents.size(); componentIndex++)
{
AZ::Component* oldComponent = oldComponents[componentIndex];
AZ::Component* newComponent = newComponents[componentIndex];
AZ::Uuid oldComponentType = oldComponent->RTTI_GetType();
AZ::Uuid newComponentType = newComponent->RTTI_GetType();
AZ_Assert(oldComponentType == newComponentType, "Components have different types");
const AZ::Uuid& classId = AZ::SerializeTypeInfo<AZ::Component>::GetUuid(oldComponent);
const AZ::SerializeContext::ClassData* classData = context->FindClassData(classId);
// we would like to be able to know what changed
for (const AZ::SerializeContext::ClassElement& element : classData->m_elements)
{
if (element.m_flags & AZ::SerializeContext::ClassElement::FLG_BASE_CLASS)
{
if (BaseClassPropertyPotentiallyChanged(context, newComponent, oldComponent, element, element.m_offset))
{
valueChanged = true;
}
}
else
{
if (HasComponentParamValueAzChanged(newComponent, oldComponent, element, element.m_offset))
{
valueChanged = true;
AzEntityPropertyChanged(oldComponent, newComponent, element, element.m_offset);
}
}
}
}
}
delete oldEntity;
// save a new cache of the current values of all the entities properties
// do this before calling OnKeysChanged because that can end up causing
// UiElementPropertyChanged to get called again
AZ::IO::ByteContainerStream<AZStd::string> saveStream(&m_azEntityDataCache);
[[maybe_unused]] bool success = AZ::Utils::SaveObjectToStream(saveStream, AZ::ObjectStream::ST_XML, pNodeEntity);
AZ_Assert(success, "Failed to serialize canvas entity to XML");
if (valueChanged)
{
GetSequence()->OnKeysChanged();
}
}
}
s_isForcingAnimationBecausePropertyChanged = false;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::AzEntityPropertyChanged(
AZ::Component* oldComponent, AZ::Component* newComponent,
const AZ::SerializeContext::ClassElement& element, size_t offset)
{
if (element.m_flags & AZ::SerializeContext::ClassElement::FLG_BASE_CLASS)
{
// this is a base class of a member within the component e.g. the base class of an asset ref
// we do not yet handle animating such values
return;
}
const float time = GetSequence()->GetTime();
UiAnimParamData param(newComponent->GetId(), element.m_name, element.m_typeId, offset);
CUiAnimViewTrack* track = GetTrackForParameterAz(param);
CUiAnimationContext* pAnimationContext = nullptr;
EBUS_EVENT_RESULT(pAnimationContext, UiEditorAnimationBus, GetAnimationContext);
if (!track && pAnimationContext->IsRecording())
{
// create a new track
track = CreateTrackAz(param);
// not sure if we really want to do this but it seems useful, if the time is not zero
// then we add a value at time zero which is the original value before this change
// that caused a track to be created
if (time != 0.0f)
{
SetComponentParamValueAz(0.0f, newComponent, oldComponent, element, offset);
}
}
// add a new value
if (track)
{
if (!pAnimationContext->IsRecording())
{
// Offset all keys by move amount.
if (element.m_typeId == AZ::SerializeTypeInfo<float>::GetUuid())
{
float prevValue;
track->GetValue(time, prevValue);
//float offset = newElementFloat - prevValue;
//track->OffsetKeyPosition(offset);
}
}
else
{
UiAnimUndo::Record(new CUndoTrackObject(track, GetSequence()));
const int flags = m_pAnimNode->GetFlags();
// Set the selected flag to enable record when unselected camera is moved through viewport
m_pAnimNode->SetFlags(flags | eUiAnimNodeFlags_EntitySelected);
SetComponentParamValueAz(time, newComponent, newComponent, element, offset);
m_pAnimNode->SetFlags(flags);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::AzCreateCompoundTrackIfNeeded(
float time, AZ::Component* newComponent, AZ::Component* oldComponent,
const AZ::SerializeContext::ClassElement& element, size_t offset)
{
UiAnimParamData param(newComponent->GetId(), element.m_name, element.m_typeId, offset);
CUiAnimViewTrack* track = GetTrackForParameterAz(param);
CUiAnimationContext* pAnimationContext = nullptr;
EBUS_EVENT_RESULT(pAnimationContext, UiEditorAnimationBus, GetAnimationContext);
if (!track && pAnimationContext->IsRecording())
{
// create a new track
track = CreateTrackAz(param);
// if time is not 0 then add the original values for all sub tracks
if (time != 0.0f)
{
SetComponentParamValueAz(0.0f, newComponent, oldComponent, element, offset);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::SetComponentParamValueAz(float time,
AZ::Component* dstComponent, AZ::Component* srcComponent,
const AZ::SerializeContext::ClassElement& element, size_t offset)
{
void* srcElementData = reinterpret_cast<char*>(srcComponent) + offset;
UiAnimParamData param(dstComponent->GetId(), element.m_name, element.m_typeId, offset);
if (element.m_typeId == AZ::SerializeTypeInfo<float>::GetUuid())
{
float srcElementFloat = *reinterpret_cast<float*>(srcElementData);
m_pAnimNode->SetParamValueAz(time, param, srcElementFloat);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<bool>::GetUuid())
{
bool srcElementValue = *reinterpret_cast<bool*>(srcElementData);
m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<int>::GetUuid())
{
// int srcElementValue = *reinterpret_cast<int*>(srcElementData);
// m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<unsigned int>::GetUuid())
{
// unsigned int srcElementValue = *reinterpret_cast<unsigned int*>(srcElementData);
// m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Vector2>::GetUuid())
{
AZ::Vector2 srcElementValue = *reinterpret_cast<AZ::Vector2*>(srcElementData);
m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Vector3>::GetUuid())
{
AZ::Vector3 srcElementValue = *reinterpret_cast<AZ::Vector3*>(srcElementData);
m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Vector4>::GetUuid())
{
AZ::Vector4 srcElementValue = *reinterpret_cast<AZ::Vector4*>(srcElementData);
m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Color>::GetUuid())
{
AZ::Color srcElementValue = *reinterpret_cast<AZ::Color*>(srcElementData);
m_pAnimNode->SetParamValueAz(time, param, srcElementValue);
}
else
{
// it is not a float
AZ::SerializeContext* context = nullptr;
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialization context found");
// it is not a float
const AZ::SerializeContext::ClassData* classData = context->FindClassData(element.m_typeId);
if (classData && !classData->m_elements.empty())
{
AzCreateCompoundTrackIfNeeded(time, dstComponent, srcComponent, element, element.m_offset);
for (const AZ::SerializeContext::ClassElement& subElement : classData->m_elements)
{
SetComponentParamValueAz(time,
dstComponent, srcComponent,
subElement, offset + subElement.m_offset);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::HasComponentParamValueAzChanged(
AZ::Component* dstComponent, AZ::Component* srcComponent,
const AZ::SerializeContext::ClassElement& element, size_t offset)
{
if (element.m_flags & AZ::SerializeContext::ClassElement::FLG_BASE_CLASS)
{
// this is a base class of a member within the component e.g. the base class of an asset ref
// we do not yet handle animating such values
return false;
}
bool changed = false;
const float floatEpsilon = 0.0001f;
void* dstElementData = reinterpret_cast<char*>(dstComponent) + offset;
void* srcElementData = reinterpret_cast<char*>(srcComponent) + offset;
if (element.m_typeId == AZ::SerializeTypeInfo<float>::GetUuid())
{
float dstElementValue = *reinterpret_cast<float*>(dstElementData);
float srcElementValue = *reinterpret_cast<float*>(srcElementData);
changed = dstElementValue != srcElementValue;
}
else if (element.m_typeId == AZ::SerializeTypeInfo<bool>::GetUuid())
{
bool dstElementValue = *reinterpret_cast<bool*>(dstElementData);
bool srcElementValue = *reinterpret_cast<bool*>(srcElementData);
changed = dstElementValue != srcElementValue;
}
else if (element.m_typeId == AZ::SerializeTypeInfo<int>::GetUuid())
{
unsigned int dstElementValue = *reinterpret_cast<int*>(dstElementData);
unsigned int srcElementValue = *reinterpret_cast<int*>(srcElementData);
changed = dstElementValue != srcElementValue;
}
else if (element.m_typeId == AZ::SerializeTypeInfo<unsigned int>::GetUuid())
{
unsigned int dstElementValue = *reinterpret_cast<unsigned int*>(dstElementData);
unsigned int srcElementValue = *reinterpret_cast<unsigned int*>(srcElementData);
changed = dstElementValue != srcElementValue;
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Vector2>::GetUuid())
{
AZ::Vector2 dstElementValue = *reinterpret_cast<AZ::Vector2*>(dstElementData);
AZ::Vector2 srcElementValue = *reinterpret_cast<AZ::Vector2*>(srcElementData);
changed = !dstElementValue.IsClose(srcElementValue, floatEpsilon);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Vector3>::GetUuid())
{
AZ::Vector3 dstElementValue = *reinterpret_cast<AZ::Vector3*>(dstElementData);
AZ::Vector3 srcElementValue = *reinterpret_cast<AZ::Vector3*>(srcElementData);
changed = !dstElementValue.IsClose(srcElementValue, floatEpsilon);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Vector4>::GetUuid())
{
AZ::Vector4 dstElementValue = *reinterpret_cast<AZ::Vector4*>(dstElementData);
AZ::Vector4 srcElementValue = *reinterpret_cast<AZ::Vector4*>(srcElementData);
changed = !dstElementValue.IsClose(srcElementValue, floatEpsilon);
}
else if (element.m_typeId == AZ::SerializeTypeInfo<AZ::Color>::GetUuid())
{
AZ::Color dstElementValue = *reinterpret_cast<AZ::Color*>(dstElementData);
AZ::Color srcElementValue = *reinterpret_cast<AZ::Color*>(srcElementData);
changed = !dstElementValue.IsClose(srcElementValue, floatEpsilon);
}
else
{
// it is not a float
AZ::SerializeContext* context = nullptr;
EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(context, "No serialization context found");
const AZ::SerializeContext::ClassData* classData = context->FindClassData(element.m_typeId);
if (classData && !classData->m_elements.empty())
{
// this is an aggregate type, try finding any floats within
// we would like to be able to know what changed
for (const AZ::SerializeContext::ClassElement& subElement : classData->m_elements)
{
if (HasComponentParamValueAzChanged(dstComponent, srcComponent,
subElement, offset + subElement.m_offset))
{
changed = true;
break;
}
}
}
}
return changed;
}
bool CUiAnimViewAnimNode::BaseClassPropertyPotentiallyChanged(
AZ::SerializeContext* context,
AZ::Component* dstComponent, AZ::Component* srcComponent,
const AZ::SerializeContext::ClassElement& element, [[maybe_unused]] size_t offset)
{
size_t baseClassOffset = element.m_offset;
const AZ::Uuid& baseClassId = element.m_typeId;
const AZ::SerializeContext::ClassData* baseClassData = context->FindClassData(baseClassId);
bool valueChanged = false;
if (baseClassData)
{
for (const AZ::SerializeContext::ClassElement& baseElement : baseClassData->m_elements)
{
size_t baseOffset = baseClassOffset + baseElement.m_offset;
if (baseElement.m_flags & AZ::SerializeContext::ClassElement::FLG_BASE_CLASS)
{
if (BaseClassPropertyPotentiallyChanged(context, dstComponent, srcComponent, baseElement, baseOffset))
{
valueChanged = true;
}
}
else
{
if (HasComponentParamValueAzChanged(dstComponent, srcComponent, baseElement, baseOffset))
{
valueChanged = true;
AzEntityPropertyChanged(srcComponent, dstComponent, baseElement, baseOffset);
}
}
}
}
return valueChanged;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::BindToEditorObjects()
{
if (!IsActive())
{
return;
}
CUiAnimViewSequenceNotificationContext context(GetSequence());
CUiAnimViewAnimNode* pDirector = GetDirector();
const bool bBelongsToActiveDirector = pDirector ? pDirector->IsActiveDirector() : true;
// if this node represents an AZ entity then register for updates
if (m_nodeEntityId.IsValid())
{
if (!UiElementChangeNotificationBus::Handler::BusIsConnected())
{
// register for change events on the AZ entity
UiElementChangeNotificationBus::Handler::BusConnect(m_nodeEntityId);
AZ::Entity* pNodeEntity = nullptr;
EBUS_EVENT_RESULT(pNodeEntity, AZ::ComponentApplicationBus, FindEntity, m_nodeEntityId);
if (pNodeEntity)
{
// save a cache of the current values of all the entities properties
AZ::IO::ByteContainerStream<AZStd::string> charStream(&m_azEntityDataCache);
[[maybe_unused]] bool success = AZ::Utils::SaveObjectToStream(charStream, AZ::ObjectStream::ST_XML, pNodeEntity);
AZ_Assert(success, "Failed to serialize canvas entity to XML");
}
}
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = (CUiAnimViewAnimNode*)pChildNode;
pChildAnimNode->BindToEditorObjects();
}
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::UnBindFromEditorObjects()
{
CUiAnimViewSequenceNotificationContext context(GetSequence());
// UI_ANIMATION_REVISIT - what of this function is really needed?
if (m_pAnimNode)
{
m_pAnimNode->SetNodeOwner(nullptr);
}
if (m_pNodeUiAnimator)
{
m_pNodeUiAnimator->UnBind(this);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = (CUiAnimViewAnimNode*)pChildNode;
pChildAnimNode->UnBindFromEditorObjects();
}
}
// if this node represents an AZ entity then unregister for updates
if (m_nodeEntityId.IsValid())
{
// unregister for change events on the Az entity
UiElementChangeNotificationBus::Handler::BusDisconnect(m_nodeEntityId);
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsBoundToEditorObjects() const
{
return m_pAnimNode ? (m_pAnimNode->GetNodeOwner() != NULL) : false;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNode* CUiAnimViewAnimNode::CreateSubNode(const QString& name, const EUiAnimNodeType animNodeType, AZ::Entity* pEntity, bool listen)
{
const bool bIsGroupNode = IsGroupNode();
AZ_Assert(bIsGroupNode, "bIsGroupNode is nullptr.");
if (!bIsGroupNode)
{
return nullptr;
}
// Create new node
IUiAnimNode* pNewAnimNode = m_pAnimSequence->CreateNode(animNodeType);
if (!pNewAnimNode)
{
return nullptr;
}
pNewAnimNode->SetName(name.toUtf8().data());
pNewAnimNode->CreateDefaultTracks();
pNewAnimNode->SetParent(m_pAnimNode.get());
CUiAnimViewAnimNodeFactory animNodeFactory;
CUiAnimViewAnimNode* pNewNode = animNodeFactory.BuildAnimNode(m_pAnimSequence, pNewAnimNode, this);
pNewNode->m_bExpanded = true;
//AzEntity type nodes should have a valid pEntity
if (animNodeType == eUiAnimNodeType_AzEntity)
{
AZ_Assert(pEntity, "AZ::Entity is nullptr.");
if (pEntity)
{
pNewNode->SetNodeEntityAz(pEntity);
}
}
pNewAnimNode->SetNodeOwner(pNewNode);
if (listen)
{
pNewNode->BindToEditorObjects();
}
AddNode(pNewNode);
UiAnimUndo::Record(new CUndoAnimNodeAdd(pNewNode));
return pNewNode;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::RemoveSubNode(CUiAnimViewAnimNode* pSubNode)
{
assert(UiAnimUndo::IsRecording());
const bool bIsGroupNode = IsGroupNode();
assert(bIsGroupNode);
if (!bIsGroupNode)
{
return;
}
UiAnimUndo::Record(new CUndoAnimNodeRemove(pSubNode));
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrack* CUiAnimViewAnimNode::CreateTrack(const CUiAnimParamType& paramType)
{
assert(UiAnimUndo::IsRecording());
if (GetTrackForParameter(paramType) && !(GetParamFlags(paramType) & IUiAnimNode::eSupportedParamFlags_MultipleTracks))
{
return nullptr;
}
// Create UI Animation system and UiAnimView track
IUiAnimTrack* pNewAnimTrack = m_pAnimNode->CreateTrack(paramType);
if (!pNewAnimTrack)
{
return nullptr;
}
CUiAnimViewTrackFactory trackFactory;
CUiAnimViewTrack* pNewTrack = trackFactory.BuildTrack(pNewAnimTrack, this, this);
AddNode(pNewTrack);
UiAnimUndo::Record(new CUndoTrackAdd(pNewTrack));
return pNewTrack;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::RemoveTrack(CUiAnimViewTrack* pTrack)
{
assert(UiAnimUndo::IsRecording());
assert(!pTrack->IsSubTrack());
if (!pTrack->IsSubTrack())
{
UiAnimUndo::Record(new CUndoTrackRemove(pTrack));
}
}
CUiAnimViewTrack* CUiAnimViewAnimNode::CreateTrackAz(const UiAnimParamData& param)
{
// assert(UiAnimUndo::IsRecording());
if (GetTrackForParameterAz(param))
{
return nullptr;
}
// Create UI Animation system and UiAnimView track
// This will create sub-tracks if needed
IUiAnimTrack* pNewAnimTrack = m_pAnimNode->CreateTrackForAzField(param);
if (!pNewAnimTrack)
{
return nullptr;
}
CUiAnimViewTrackFactory trackFactory;
CUiAnimViewTrack* pNewTrack = trackFactory.BuildTrack(pNewAnimTrack, this, this);
AddNode(pNewTrack);
UiAnimUndo::Record(new CUndoTrackAdd(pNewTrack));
return pNewTrack;
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::SnapTimeToPrevKey(float& time) const
{
const float startTime = time;
float closestTrackTime = std::numeric_limits<float>::min();
bool bFoundPrevKey = false;
for (size_t i = 0; i < m_childNodes.size(); ++i)
{
const CUiAnimViewNode* pNode = m_childNodes[i].get();
float closestNodeTime = startTime;
if (pNode->SnapTimeToPrevKey(closestNodeTime))
{
closestTrackTime = std::max(closestNodeTime, closestTrackTime);
bFoundPrevKey = true;
}
}
if (bFoundPrevKey)
{
time = closestTrackTime;
}
return bFoundPrevKey;
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::SnapTimeToNextKey(float& time) const
{
const float startTime = time;
float closestTrackTime = std::numeric_limits<float>::max();
bool bFoundNextKey = false;
for (size_t i = 0; i < m_childNodes.size(); ++i)
{
const CUiAnimViewNode* pNode = m_childNodes[i].get();
float closestNodeTime = startTime;
if (pNode->SnapTimeToNextKey(closestNodeTime))
{
closestTrackTime = std::min(closestNodeTime, closestTrackTime);
bFoundNextKey = true;
}
}
if (bFoundNextKey)
{
time = closestTrackTime;
}
return bFoundNextKey;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewKeyBundle CUiAnimViewAnimNode::GetSelectedKeys()
{
CUiAnimViewKeyBundle bundle;
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
bundle.AppendKeyBundle((*iter)->GetSelectedKeys());
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewKeyBundle CUiAnimViewAnimNode::GetAllKeys()
{
CUiAnimViewKeyBundle bundle;
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
bundle.AppendKeyBundle((*iter)->GetAllKeys());
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewKeyBundle CUiAnimViewAnimNode::GetKeysInTimeRange(const float t0, const float t1)
{
CUiAnimViewKeyBundle bundle;
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
bundle.AppendKeyBundle((*iter)->GetKeysInTimeRange(t0, t1));
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrackBundle CUiAnimViewAnimNode::GetAllTracks()
{
return GetTracks(false, CUiAnimParamType());
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrackBundle CUiAnimViewAnimNode::GetSelectedTracks()
{
return GetTracks(true, CUiAnimParamType());
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrackBundle CUiAnimViewAnimNode::GetTracksByParam(const CUiAnimParamType& paramType)
{
return GetTracks(false, paramType);
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrackBundle CUiAnimViewAnimNode::GetTracks(const bool bOnlySelected, const CUiAnimParamType& paramType)
{
CUiAnimViewTrackBundle bundle;
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pNode = (*iter).get();
if (pNode->GetNodeType() == eUiAVNT_Track)
{
CUiAnimViewTrack* pTrack = static_cast<CUiAnimViewTrack*>(pNode);
if (paramType != eUiAnimParamType_Invalid && pTrack->GetParameterType() != paramType)
{
continue;
}
if (!bOnlySelected || pTrack->IsSelected())
{
bundle.AppendTrack(pTrack);
}
const unsigned int subTrackCount = pTrack->GetChildCount();
for (unsigned int subTrackIndex = 0; subTrackIndex < subTrackCount; ++subTrackIndex)
{
CUiAnimViewTrack* pSubTrack = static_cast<CUiAnimViewTrack*>(pTrack->GetChild(subTrackIndex));
if (!bOnlySelected || pSubTrack->IsSelected())
{
bundle.AppendTrack(pSubTrack);
}
}
}
else if (pNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pAnimNode = static_cast<CUiAnimViewAnimNode*>(pNode);
bundle.AppendTrackBundle(pAnimNode->GetTracks(bOnlySelected, paramType));
}
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
EUiAnimNodeType CUiAnimViewAnimNode::GetType() const
{
return m_pAnimNode ? m_pAnimNode->GetType() : eUiAnimNodeType_Invalid;
}
//////////////////////////////////////////////////////////////////////////
EUiAnimNodeFlags CUiAnimViewAnimNode::GetFlags() const
{
return m_pAnimNode ? (EUiAnimNodeFlags)m_pAnimNode->GetFlags() : (EUiAnimNodeFlags)0;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::SetAsActiveDirector()
{
if (GetType() == eUiAnimNodeType_Director)
{
m_pAnimSequence->SetActiveDirector(m_pAnimNode.get());
GetSequence()->UnBindFromEditorObjects();
GetSequence()->BindToEditorObjects();
GetSequence()->OnNodeChanged(this, IUiAnimViewSequenceListener::eNodeChangeType_SetAsActiveDirector);
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsActiveDirector() const
{
return m_pAnimNode.get() == m_pAnimSequence->GetActiveDirector();
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsParamValid(const CUiAnimParamType& param) const
{
return m_pAnimNode ? m_pAnimNode->IsParamValid(param) : false;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrack* CUiAnimViewAnimNode::GetTrackForParameter(const CUiAnimParamType& paramType, uint32 index) const
{
uint32 currentIndex = 0;
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pNode = (*iter).get();
if (pNode->GetNodeType() == eUiAVNT_Track)
{
CUiAnimViewTrack* pTrack = static_cast<CUiAnimViewTrack*>(pNode);
if (pTrack->GetParameterType() == paramType)
{
if (currentIndex == index)
{
return pTrack;
}
else
{
++currentIndex;
}
}
if (pTrack->IsCompoundTrack())
{
unsigned int numChildTracks = pTrack->GetChildCount();
for (unsigned int i = 0; i < numChildTracks; ++i)
{
CUiAnimViewTrack* pChildTrack = static_cast<CUiAnimViewTrack*>(pTrack->GetChild(i));
if (pChildTrack->GetParameterType() == paramType)
{
if (currentIndex == index)
{
return pChildTrack;
}
else
{
++currentIndex;
}
}
}
}
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewTrack* CUiAnimViewAnimNode::GetTrackForParameterAz(const UiAnimParamData& param) const
{
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pNode = (*iter).get();
if (pNode->GetNodeType() == eUiAVNT_Track)
{
CUiAnimViewTrack* pTrack = static_cast<CUiAnimViewTrack*>(pNode);
if (pTrack->GetParameterType() == eUiAnimParamType_AzComponentField)
{
if (pTrack->GetParamData() == param)
{
return pTrack;
}
}
if (pTrack->IsCompoundTrack())
{
unsigned int numChildTracks = pTrack->GetChildCount();
for (unsigned int i = 0; i < numChildTracks; ++i)
{
CUiAnimViewTrack* pChildTrack = static_cast<CUiAnimViewTrack*>(pTrack->GetChild(i));
if (pChildTrack->GetParameterType() == eUiAnimParamType_AzComponentField)
{
if (pChildTrack->GetParameterType() == eUiAnimParamType_AzComponentField)
{
if (pChildTrack->GetParamData() == param)
{
return pChildTrack;
}
}
}
}
}
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::Render(const SUiAnimContext& ac)
{
if (m_pNodeUiAnimator && IsActive())
{
m_pNodeUiAnimator->Render(this, ac);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
pChildAnimNode->Render(ac);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::Animate(const SUiAnimContext& animContext)
{
if (m_pNodeUiAnimator && IsActive())
{
m_pNodeUiAnimator->Animate(this, animContext);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
pChildAnimNode->Animate(animContext);
}
}
if (!s_isForcingAnimationBecausePropertyChanged)
{
GetIEditor()->Notify(eNotify_OnUpdateViewports);
}
// save a new cache of the current values of all the entities properties,
// animating or manually moving the record head will change the component properties
// so we need to update our cache so we can spot user edits to properties
if (m_nodeEntityId.IsValid())
{
AZ::Entity* pNodeEntity = nullptr;
EBUS_EVENT_RESULT(pNodeEntity, AZ::ComponentApplicationBus, FindEntity, m_nodeEntityId);
if (pNodeEntity)
{
AZ::IO::ByteContainerStream<AZStd::string> saveStream(&m_azEntityDataCache);
[[maybe_unused]] bool success = AZ::Utils::SaveObjectToStream(saveStream, AZ::ObjectStream::ST_XML, pNodeEntity);
AZ_Assert(success, "Failed to serialize canvas entity to XML");
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::SetName(const char* pName)
{
// Check if the node's director already contains a node with this name
CUiAnimViewAnimNode* pDirector = GetDirector();
pDirector = pDirector ? pDirector : GetSequence();
CUiAnimViewAnimNodeBundle nodes = pDirector->GetAnimNodesByName(pName);
const uint numNodes = nodes.GetCount();
for (uint i = 0; i < numNodes; ++i)
{
if (nodes.GetNode(i) != this)
{
return false;
}
}
string oldName = GetName();
m_pAnimNode->SetName(pName);
if (UiAnimUndo::IsRecording())
{
UiAnimUndo::Record(new CUndoAnimNodeRename(this, oldName));
}
GetSequence()->OnNodeRenamed(this, oldName);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::CanBeRenamed() const
{
return (GetFlags() & eUiAnimNodeFlags_CanChangeName) != 0;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::SetNodeEntityAz(AZ::Entity* pEntity)
{
m_nodeEntityId = (pEntity) ? pEntity->GetId() : AZ::EntityId();
if (m_pAnimNode)
{
m_pAnimNode->SetNodeOwner(this);
EBUS_EVENT_ID(m_pAnimNode.get(), UiAnimNodeBus, SetAzEntity, pEntity);
}
GetSequence()->OnNodeChanged(this, IUiAnimViewSequenceListener::eNodeChangeType_NodeOwnerChanged);
}
//////////////////////////////////////////////////////////////////////////
AZ::Entity* CUiAnimViewAnimNode::GetNodeEntityAz([[maybe_unused]] const bool bSearch)
{
if (m_pAnimNode)
{
if (m_nodeEntityId.IsValid())
{
AZ::Entity* pNodeEntity = nullptr;
EBUS_EVENT_RESULT(pNodeEntity, AZ::ComponentApplicationBus, FindEntity, m_nodeEntityId);
return pNodeEntity;
}
}
return nullptr;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNodeBundle CUiAnimViewAnimNode::GetAllAnimNodes()
{
CUiAnimViewAnimNodeBundle bundle;
if (GetNodeType() == eUiAVNT_AnimNode)
{
bundle.AppendAnimNode(this);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
bundle.AppendAnimNodeBundle(pChildAnimNode->GetAllAnimNodes());
}
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNodeBundle CUiAnimViewAnimNode::GetSelectedAnimNodes()
{
CUiAnimViewAnimNodeBundle bundle;
if ((GetNodeType() == eUiAVNT_AnimNode || GetNodeType() == eUiAVNT_Sequence) && IsSelected())
{
bundle.AppendAnimNode(this);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
bundle.AppendAnimNodeBundle(pChildAnimNode->GetSelectedAnimNodes());
}
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNodeBundle CUiAnimViewAnimNode::GetAllOwnedNodes(const AZ::Entity* pOwner)
{
CUiAnimViewAnimNodeBundle bundle;
if (GetNodeType() == eUiAVNT_AnimNode && GetNodeEntityAz() == pOwner)
{
bundle.AppendAnimNode(this);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
bundle.AppendAnimNodeBundle(pChildAnimNode->GetAllOwnedNodes(pOwner));
}
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNodeBundle CUiAnimViewAnimNode::GetAnimNodesByType(EUiAnimNodeType animNodeType)
{
CUiAnimViewAnimNodeBundle bundle;
if (GetNodeType() == eUiAVNT_AnimNode && GetType() == animNodeType)
{
bundle.AppendAnimNode(this);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
bundle.AppendAnimNodeBundle(pChildAnimNode->GetAnimNodesByType(animNodeType));
}
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNodeBundle CUiAnimViewAnimNode::GetAnimNodesByName(const char* pName)
{
CUiAnimViewAnimNodeBundle bundle;
QString nodeName = GetName();
if (GetNodeType() == eUiAVNT_AnimNode && QString::compare(pName, nodeName, Qt::CaseInsensitive) == 0)
{
bundle.AppendAnimNode(this);
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
bundle.AppendAnimNodeBundle(pChildAnimNode->GetAnimNodesByName(pName));
}
}
return bundle;
}
//////////////////////////////////////////////////////////////////////////
const char* CUiAnimViewAnimNode::GetParamName(const CUiAnimParamType& paramType) const
{
const char* pName = m_pAnimNode->GetParamName(paramType);
return pName ? pName : "";
}
//////////////////////////////////////////////////////////////////////////
const char* CUiAnimViewAnimNode::GetParamNameForTrack(const CUiAnimParamType& paramType, const IUiAnimTrack* track) const
{
const char* pName = m_pAnimNode->GetParamNameForTrack(paramType, track);
return pName ? pName : "";
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsGroupNode() const
{
return GetType() == eUiAnimNodeType_Director || GetType() == eUiAnimNodeType_Group
|| GetType() == eUiAnimNodeType_AzEntity;
}
//////////////////////////////////////////////////////////////////////////
QString CUiAnimViewAnimNode::GetAvailableNodeNameStartingWith(const QString& name) const
{
QString newName = name;
unsigned int index = 2;
while (const_cast<CUiAnimViewAnimNode*>(this)->GetAnimNodesByName(newName.toUtf8().data()).GetCount() > 0)
{
newName = QStringLiteral("%1%2").arg(name).arg(index);
++index;
}
return newName;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimViewAnimNodeBundle CUiAnimViewAnimNode::AddSelectedUiElements()
{
assert(IsGroupNode());
//assert(UiAnimUndo::IsRecording());
CUiAnimViewAnimNodeBundle addedNodes;
// Add selected nodes.
// Need some way to communicate with the UiCanvasEditor here
LyShine::EntityArray selectedElements;
EBUS_EVENT_RESULT(selectedElements, UiEditorDLLBus, GetSelectedElements);
for (AZ::Entity* entity : selectedElements)
{
// Check if object already assigned to some AnimNode.
CUiAnimViewAnimNode* pExistingNode = CUiAnimViewSequenceManager::GetSequenceManager()->GetActiveAnimNode(entity);
if (pExistingNode)
{
// If it has the same director than the current node, reject it
// Actually for Az Entities this fails because of the component nodes
// if (pExistingNode->GetDirector() == GetDirector())
{
continue;
}
}
// Get node type (either entity or camera)
CUiAnimViewAnimNode* pAnimNode = nullptr;
// Since entity names in canvases do not tend to be unique add the element ID
LyShine::ElementId elementId = 0;
EBUS_EVENT_ID_RESULT(elementId, entity->GetId(), UiElementBus, GetElementId);
QString nodeName = entity->GetName().c_str();
char suffix[10];
azsnprintf(suffix, 10, " (%d)", elementId);
nodeName += suffix;
pAnimNode = CreateSubNode(nodeName, eUiAnimNodeType_AzEntity, entity, true);
if (pAnimNode)
{
addedNodes.AppendAnimNode(pAnimNode);
}
}
return addedNodes;
}
//////////////////////////////////////////////////////////////////////////
unsigned int CUiAnimViewAnimNode::GetParamCount() const
{
return m_pAnimNode ? m_pAnimNode->GetParamCount() : 0;
}
//////////////////////////////////////////////////////////////////////////
CUiAnimParamType CUiAnimViewAnimNode::GetParamType(unsigned int index) const
{
unsigned int paramCount = GetParamCount();
if (!m_pAnimNode || index >= paramCount)
{
return eUiAnimParamType_Invalid;
}
return m_pAnimNode->GetParamType(index);
}
//////////////////////////////////////////////////////////////////////////
IUiAnimNode::ESupportedParamFlags CUiAnimViewAnimNode::GetParamFlags(const CUiAnimParamType& paramType) const
{
if (m_pAnimNode)
{
return m_pAnimNode->GetParamFlags(paramType);
}
return IUiAnimNode::ESupportedParamFlags(0);
}
//////////////////////////////////////////////////////////////////////////
EUiAnimValue CUiAnimViewAnimNode::GetParamValueType(const CUiAnimParamType& paramType) const
{
if (m_pAnimNode)
{
return m_pAnimNode->GetParamValueType(paramType);
}
return eUiAnimValue_Unknown;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::UpdateDynamicParams()
{
if (m_pAnimNode)
{
m_pAnimNode->UpdateDynamicParams();
}
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
pChildAnimNode->UpdateDynamicParams();
}
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::CopyKeysToClipboard(XmlNodeRef& xmlNode, const bool bOnlySelectedKeys, const bool bOnlyFromSelectedTracks)
{
XmlNodeRef childNode = xmlNode->createNode("Node");
childNode->setAttr("name", GetName());
childNode->setAttr("type", GetType());
for (auto iter = m_childNodes.begin(); iter != m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
pChildNode->CopyKeysToClipboard(childNode, bOnlySelectedKeys, bOnlyFromSelectedTracks);
}
if (childNode->getChildCount() > 0)
{
xmlNode->addChild(childNode);
}
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::CopyNodesToClipboard(const bool bOnlySelected, QWidget* context)
{
XmlNodeRef animNodesRoot = XmlHelpers::CreateXmlNode("CopyAnimNodesRoot");
CopyNodesToClipboardRec(this, animNodesRoot, bOnlySelected);
CClipboard clipboard(context);
clipboard.Put(animNodesRoot, "Track view entity nodes");
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::CopyNodesToClipboardRec(CUiAnimViewAnimNode* pCurrentAnimNode, XmlNodeRef& xmlNode, const bool bOnlySelected)
{
if (!pCurrentAnimNode->IsGroupNode() && (!bOnlySelected || pCurrentAnimNode->IsSelected()))
{
XmlNodeRef childXmlNode = xmlNode->newChild("Node");
pCurrentAnimNode->m_pAnimNode->Serialize(childXmlNode, false, true);
}
for (auto iter = pCurrentAnimNode->m_childNodes.begin(); iter != pCurrentAnimNode->m_childNodes.end(); ++iter)
{
CUiAnimViewNode* pChildNode = (*iter).get();
if (pChildNode->GetNodeType() == eUiAVNT_AnimNode)
{
CUiAnimViewAnimNode* pChildAnimNode = static_cast<CUiAnimViewAnimNode*>(pChildNode);
// If selected and group node, force copying of children
const bool bSelectedAndGroupNode = pCurrentAnimNode->IsSelected() && pCurrentAnimNode->IsGroupNode();
CopyNodesToClipboardRec(pChildAnimNode, xmlNode, !bSelectedAndGroupNode && bOnlySelected);
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::PasteNodesFromClipboard(QWidget* context)
{
assert(UiAnimUndo::IsRecording());
CClipboard clipboard(context);
if (clipboard.IsEmpty())
{
return false;
}
XmlNodeRef animNodesRoot = clipboard.Get();
if (animNodesRoot == NULL || strcmp(animNodesRoot->getTag(), "CopyAnimNodesRoot") != 0)
{
return false;
}
const bool bLightAnimationSetActive = GetSequence()->GetFlags() & IUiAnimSequence::eSeqFlags_LightAnimationSet;
const unsigned int numNodes = animNodesRoot->getChildCount();
for (int i = 0; i < numNodes; ++i)
{
XmlNodeRef xmlNode = animNodesRoot->getChild(i);
int type;
if (!xmlNode->getAttr("Type", type))
{
continue;
}
if (bLightAnimationSetActive && (EUiAnimNodeType)type != eUiAnimNodeType_Light)
{
// Ignore non light nodes in light animation set
continue;
}
PasteNodeFromClipboard(xmlNode);
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::PasteNodeFromClipboard(XmlNodeRef xmlNode)
{
QString name;
if (!xmlNode->getAttr("Name", name))
{
return;
}
const bool bIsGroupNode = IsGroupNode();
assert(bIsGroupNode);
if (!bIsGroupNode)
{
return;
}
// Check if the node's director or sequence already contains a node with this name
CUiAnimViewAnimNode* pDirector = GetDirector();
pDirector = pDirector ? pDirector : GetSequence();
if (pDirector->GetAnimNodesByName(name.toUtf8().data()).GetCount() > 0)
{
return;
}
// Create UI Animation system and UiAnimView node
IUiAnimNode* pNewAnimNode = m_pAnimSequence->CreateNode(xmlNode);
if (!pNewAnimNode)
{
return;
}
pNewAnimNode->SetParent(m_pAnimNode.get());
CUiAnimViewAnimNodeFactory animNodeFactory;
CUiAnimViewAnimNode* pNewNode = animNodeFactory.BuildAnimNode(m_pAnimSequence, pNewAnimNode, this);
pNewNode->m_bExpanded = true;
AddNode(pNewNode);
UiAnimUndo::Record(new CUndoAnimNodeAdd(pNewNode));
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsValidReparentingTo(CUiAnimViewAnimNode* pNewParent)
{
// UI_ANIMATION_REVISIT, do we want to support any reparenting?
if (pNewParent == GetParentNode() || !pNewParent->IsGroupNode())
{
return false;
}
// Check if the new parent already contains a node with this name
CUiAnimViewAnimNodeBundle foundNodes = pNewParent->GetAnimNodesByName(GetName());
if (foundNodes.GetCount() > 1 || (foundNodes.GetCount() == 1 && foundNodes.GetNode(0) != this))
{
return false;
}
// Check if another node already owns this entity in the new parent's tree
AZ::Entity* pOwner = GetNodeEntityAz();
if (pOwner)
{
CUiAnimViewAnimNodeBundle ownedNodes = pNewParent->GetAllOwnedNodes(pOwner);
if (ownedNodes.GetCount() > 0 && ownedNodes.GetNode(0) != this)
{
return false;
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::SetNewParent(CUiAnimViewAnimNode* pNewParent)
{
if (pNewParent == GetParentNode())
{
return;
}
assert(UiAnimUndo::IsRecording());
assert(IsValidReparentingTo(pNewParent));
UiAnimUndo::Record(new CUndoAnimNodeReparent(this, pNewParent));
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::SetDisabled(bool bDisabled)
{
if (m_pAnimNode)
{
if (bDisabled)
{
m_pAnimNode->SetFlags(m_pAnimNode->GetFlags() | eUiAnimNodeFlags_Disabled);
GetSequence()->OnNodeChanged(this, IUiAnimViewSequenceListener::eNodeChangeType_Disabled);
}
else
{
m_pAnimNode->SetFlags(m_pAnimNode->GetFlags() & ~eUiAnimNodeFlags_Disabled);
GetSequence()->OnNodeChanged(this, IUiAnimViewSequenceListener::eNodeChangeType_Enabled);
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsDisabled() const
{
return m_pAnimNode ? m_pAnimNode->GetFlags() & eUiAnimNodeFlags_Disabled : false;
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::IsActive()
{
CUiAnimViewSequence* pSequence = GetSequence();
const bool bInActiveSequence = pSequence ? GetSequence()->IsBoundToEditorObjects() : false;
CUiAnimViewAnimNode* pDirector = GetDirector();
const bool bMemberOfActiveDirector = pDirector ? GetDirector()->IsActiveDirector() : true;
return bInActiveSequence && bMemberOfActiveDirector;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::OnSelectionChanged(const bool bSelected)
{
if (m_pAnimNode)
{
const EUiAnimNodeType animNodeType = GetType();
assert(animNodeType == eUiAnimNodeType_Camera || animNodeType == eUiAnimNodeType_Entity || animNodeType == eUiAnimNodeType_GeomCache);
const EUiAnimNodeFlags flags = (EUiAnimNodeFlags)m_pAnimNode->GetFlags();
m_pAnimNode->SetFlags(bSelected ? (flags | eUiAnimNodeFlags_EntitySelected) : (flags & ~eUiAnimNodeFlags_EntitySelected));
}
}
//////////////////////////////////////////////////////////////////////////
bool CUiAnimViewAnimNode::CheckTrackAnimated(const CUiAnimParamType& paramType) const
{
if (!m_pAnimNode)
{
return false;
}
CUiAnimViewTrack* pTrack = GetTrackForParameter(paramType);
return pTrack && pTrack->GetKeyCount() > 0;
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::OnNodeUiAnimated([[maybe_unused]] IUiAnimNode* pNode)
{
// UI ANIMATION_REVISIT - is this function needed?
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::OnNodeVisibilityChanged([[maybe_unused]] IUiAnimNode* pNode, [[maybe_unused]] const bool bHidden)
{
// UI ANIMATION_REVISIT - is this function needed?
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::OnNodeReset([[maybe_unused]] IUiAnimNode* pNode)
{
// UI ANIMATION_REVISIT - is this function needed?
}
//////////////////////////////////////////////////////////////////////////
void CUiAnimViewAnimNode::OnDone()
{
SetNodeEntityAz(nullptr);
}