Files
UnrealEngine/Engine/Source/Runtime/MovieScene/Private/MovieSceneFolder.cpp
2025-05-18 13:04:45 +08:00

436 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MovieSceneFolder.h"
#include "MovieScene.h"
#include "MovieSceneTrack.h"
#include "Algo/Count.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneFolder)
void GetMovieSceneFoldersRecursive(TArrayView<UMovieSceneFolder* const> InFoldersToRecurse, TArray<UMovieSceneFolder*>& OutFolders)
{
for (UMovieSceneFolder* Folder : InFoldersToRecurse)
{
if (Folder)
{
OutFolders.Add(Folder);
GetMovieSceneFoldersRecursive(Folder->GetChildFolders(), OutFolders);
}
}
}
UMovieSceneFolder::UMovieSceneFolder( const FObjectInitializer& ObjectInitializer )
: Super( ObjectInitializer )
#if WITH_EDITORONLY_DATA
, FolderColor(FColor::White)
, SortingOrder(-1)
#endif
{
}
FName UMovieSceneFolder::GetFolderName() const
{
return FolderName;
}
void UMovieSceneFolder::SetFolderName( FName InFolderName )
{
Modify();
FolderName = InFolderName;
}
TArrayView<UMovieSceneFolder* const> UMovieSceneFolder::GetChildFolders() const
{
return ChildFolders;
}
void UMovieSceneFolder::AddChildFolder( UMovieSceneFolder* InChildFolder )
{
Modify();
#if WITH_EDITORONLY_DATA
// Ensure the added folder does not belong to any other folder in the same scene.
UMovieScene* OwningScene = GetTypedOuter<UMovieScene>();
if (OwningScene)
{
TArray<UMovieSceneFolder*> AllFolders;
GetMovieSceneFoldersRecursive(OwningScene->GetRootFolders(), AllFolders);
for (UMovieSceneFolder* MovieSceneFolder : AllFolders)
{
MovieSceneFolder->RemoveChildFolder(InChildFolder);
}
if (OwningScene->GetRootFolders().Contains(InChildFolder))
{
OwningScene->Modify();
OwningScene->RemoveRootFolder(InChildFolder);
}
}
#endif
// Now add it as a child of ourself
ChildFolders.Add( InChildFolder );
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnChildFolderAdded, InChildFolder);
}
void UMovieSceneFolder::RemoveChildFolder( UMovieSceneFolder* InChildFolder )
{
Modify();
ChildFolders.Remove(InChildFolder);
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnChildFolderRemoved, InChildFolder);
}
const TArray<UMovieSceneTrack*>& UMovieSceneFolder::GetChildTracks() const
{
return ChildTracks;
}
void UMovieSceneFolder::AddChildTrack( UMovieSceneTrack* InTrack )
{
Modify();
#if WITH_EDITORONLY_DATA
// Ensure the added track does not belong to any other folder in the same scene.
UMovieScene* OwningScene = GetTypedOuter<UMovieScene>();
if (OwningScene)
{
TArray<UMovieSceneFolder*> AllFolders;
GetMovieSceneFoldersRecursive(OwningScene->GetRootFolders(), AllFolders);
for (UMovieSceneFolder* MovieSceneFolder : AllFolders)
{
MovieSceneFolder->RemoveChildTrack(InTrack);
}
}
#endif
ChildTracks.Add( InTrack );
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnTrackAdded, InTrack);
}
void UMovieSceneFolder::RemoveChildTrack( UMovieSceneTrack* InTrack )
{
Modify();
if (ChildTracks.Remove( InTrack ) > 0)
{
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnTrackRemoved, InTrack);
}
}
void UMovieSceneFolder::ClearChildTracks()
{
Modify();
ChildTracks.Empty();
}
const TArray<FGuid>& UMovieSceneFolder::GetChildObjectBindings() const
{
return ChildObjectBindings;
}
void UMovieSceneFolder::AddChildObjectBinding(const FGuid& InObjectBinding )
{
Modify();
#if WITH_EDITORONLY_DATA
// Ensure the added object does not belong to any other folder in the same scene.
UMovieScene* OwningScene = GetTypedOuter<UMovieScene>();
if (OwningScene)
{
TArray<UMovieSceneFolder*> AllFolders;
GetMovieSceneFoldersRecursive(OwningScene->GetRootFolders(), AllFolders);
for (UMovieSceneFolder* MovieSceneFolder : AllFolders)
{
MovieSceneFolder->RemoveChildObjectBinding(InObjectBinding);
}
}
#endif
ChildObjectBindings.Add( InObjectBinding );
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnObjectBindingAdded, InObjectBinding);
}
void UMovieSceneFolder::RemoveChildObjectBinding( const FGuid& InObjectBinding )
{
Modify();
if (ChildObjectBindings.Remove( InObjectBinding ) > 0)
{
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnObjectBindingRemoved, InObjectBinding);
}
}
void UMovieSceneFolder::ClearChildObjectBindings()
{
Modify();
ChildObjectBindings.Empty();
}
void UMovieSceneFolder::PostLoad()
{
// Remove any null folders
for (int32 ChildFolderIndex = 0; ChildFolderIndex < ChildFolders.Num(); )
{
if (ChildFolders[ChildFolderIndex] == nullptr)
{
ChildFolders.RemoveAt(ChildFolderIndex);
}
else
{
++ChildFolderIndex;
}
}
#if WITH_EDITORONLY_DATA
// Historically we've not been very strict about ensuring a folder, track, or object binding existed
// only in one folder. This is now enforced (via automatically removing the item from other folders
// when they are added to this folder), and checked (the tree view trips an ensure on invalid children)
// but all legacy content can still have the invalid children which continuously trips the ensure.
// Since we now enforce child-only-exists-in-one-folder, we can safely remove any invalid children on
// load, and be confident that we shouldn't run into situations in the future where an invalid child is
// left in a folder.
UMovieScene* OwningScene = GetTypedOuter<UMovieScene>();
if (OwningScene)
{
// Validate child Tracks
for(int32 ChildTrackIndex = 0; ChildTrackIndex < ChildTracks.Num(); ChildTrackIndex++)
{
const UMovieSceneTrack* ChildTrack = ChildTracks[ChildTrackIndex];
if (!OwningScene->GetTracks().Contains(ChildTrack))
{
ChildTracks.RemoveAt(ChildTrackIndex);
ChildTrackIndex--;
UE_LOG(LogMovieScene, Warning, TEXT("Folder (%s) in Sequence (%s) contained a reference to a Track (%s) that no longer exists in the sequence, removing."), *GetFolderName().ToString(), *OwningScene->GetPathName(), *GetNameSafe(ChildTrack));
}
}
// Validate child Object Bindings
for (int32 ChildObjectBindingIndex = 0; ChildObjectBindingIndex < ChildObjectBindings.Num(); ChildObjectBindingIndex++)
{
const FGuid& ChildBinding = ChildObjectBindings[ChildObjectBindingIndex];
if (!OwningScene->FindBinding(ChildBinding))
{
UE_LOG(LogMovieScene, Warning, TEXT("Folder (%s) in Sequence (%s) contained a reference to an Object Binding (%s) that no longer exists in the sequence, removing."), *GetFolderName().ToString(), *OwningScene->GetPathName(), *ChildBinding.ToString());
ChildObjectBindings.RemoveAt(ChildObjectBindingIndex);
ChildObjectBindingIndex--;
}
}
// A folder should exist in only one place in the tree, as a child of ourself. If they exist in more
// than one place, two folders point to the same actual UObject, so we'll remove it from ourself. When
// the that folder is PostLoaded it will search the whole tree again and only find the one reference as
// our reference will no longer exist.
TArray<UMovieSceneFolder*> AllFolders;
GetMovieSceneFoldersRecursive(OwningScene->GetRootFolders(), AllFolders);
for (int32 ChildFolderIndex = 0; ChildFolderIndex < ChildFolders.Num(); ChildFolderIndex++)
{
int32 NumFolderInstances = Algo::Count(AllFolders, ChildFolders[ChildFolderIndex]);
if (NumFolderInstances > 1)
{
UE_LOG(LogMovieScene, Warning, TEXT("Folder (%s) in Sequence (%s) contained a reference to an Folder (%s) that exists in multiple places in the sequence, removing."), *GetFolderName().ToString(), *OwningScene->GetPathName(), *ChildFolders[ChildFolderIndex]->GetFolderName().ToString());
ChildFolders.RemoveAt(ChildFolderIndex);
ChildFolderIndex--;
}
}
}
#endif
Super::PostLoad();
}
UMovieSceneFolder* UMovieSceneFolder::FindFolderContaining(const FGuid& InObjectBinding)
{
for (FGuid ChildGuid : GetChildObjectBindings())
{
if (ChildGuid == InObjectBinding)
{
return this;
}
}
for (UMovieSceneFolder* ChildFolder : GetChildFolders())
{
UMovieSceneFolder* Folder = ChildFolder->FindFolderContaining(InObjectBinding);
if (Folder != nullptr)
{
return Folder;
}
}
return nullptr;
}
UMovieSceneFolder* UMovieSceneFolder::FindFolderContaining(const UMovieSceneTrack* InTrack)
{
if (ChildTracks.Contains(InTrack))
{
return this;
}
for (UMovieSceneFolder* ChildFolder : GetChildFolders())
{
UMovieSceneFolder* Folder = ChildFolder->FindFolderContaining(InTrack);
if (Folder != nullptr)
{
return Folder;
}
}
return nullptr;
}
void
TraverseFolder(UMovieSceneFolder* Folder, TMap<UMovieSceneFolder*, UMovieSceneFolder*>& ChildToParentMap)
{
for (UMovieSceneFolder* Child : Folder->GetChildFolders())
{
ChildToParentMap.Add(Child, Folder);
TraverseFolder(Child, ChildToParentMap);
}
}
void UMovieSceneFolder::CalculateFolderPath(UMovieSceneFolder* Folder, TArrayView<UMovieSceneFolder* const> RootFolders, TArray<FName>& FolderPath)
{
TMap<UMovieSceneFolder*, UMovieSceneFolder*> ChildToParentMap;
for (UMovieSceneFolder* RootFolder : RootFolders)
{
TraverseFolder(RootFolder, ChildToParentMap);
}
FolderPath.Add(Folder->GetFolderName());
UMovieSceneFolder* Parent = ChildToParentMap.Contains(Folder) ? ChildToParentMap[Folder] : nullptr;
while (Parent)
{
FolderPath.Insert(Parent->GetFolderName(), 0);
Parent = ChildToParentMap.Contains(Parent) ? ChildToParentMap[Parent] : nullptr;
}
}
UMovieSceneFolder* UMovieSceneFolder::GetFolderWithPath(const TArray<FName>& InFolderPath, const TArray<UMovieSceneFolder*>& InFolders, TArrayView<UMovieSceneFolder* const> RootFolders)
{
for (UMovieSceneFolder* Folder : InFolders)
{
TArray<FName> FolderPath;
UMovieSceneFolder::CalculateFolderPath(Folder, RootFolders, FolderPath);
if (FolderPath == InFolderPath)
{
return Folder;
}
}
return nullptr;
}
void UMovieSceneFolder::Serialize( FArchive& Archive )
{
if ( Archive.IsLoading() )
{
Super::Serialize( Archive );
#if WITH_EDITOR
if (ChildMasterTracks_DEPRECATED.Num())
{
ChildTracks = ChildMasterTracks_DEPRECATED;
ChildMasterTracks_DEPRECATED.Empty();
}
#endif
ChildObjectBindings.Empty();
for ( const FString& ChildObjectBindingString : ChildObjectBindingStrings )
{
FGuid ChildObjectBinding;
FGuid::Parse( ChildObjectBindingString, ChildObjectBinding );
ChildObjectBindings.Add( ChildObjectBinding );
}
}
else
{
ChildObjectBindingStrings.Empty();
for ( const FGuid& ChildObjectBinding : ChildObjectBindings )
{
ChildObjectBindingStrings.Add( ChildObjectBinding.ToString() );
}
Super::Serialize( Archive );
}
}
FName UMovieSceneFolder::MakeUniqueChildFolderName(FName InName) const
{
return MakeUniqueChildFolderName(InName, GetChildFolders());
}
FName UMovieSceneFolder::MakeUniqueChildFolderName(FName InName, TArrayView<UMovieSceneFolder* const> InFolders)
{
bool bFoundExactDuplicate = false;
int32 NextNameIndex = InName.GetNumber();
// Iterate all children, finding a new unique name index for any that have the same base name
for (UMovieSceneFolder* Child : InFolders)
{
constexpr bool bCompareNumber = false;
if (InName.IsEqual(Child->GetFolderName(), ENameCase::IgnoreCase, bCompareNumber))
{
NextNameIndex = FMath::Max(NextNameIndex, Child->GetFolderName().GetNumber()) + 1;
}
if (InName == Child->GetFolderName())
{
bFoundExactDuplicate = true;
}
}
if (bFoundExactDuplicate)
{
InName.SetNumber(NextNameIndex);
}
return InName;
}
#if WITH_EDITOR
void UMovieSceneFolder::PostEditUndo()
{
Super::PostEditUndo();
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnPostUndo);
}
void UMovieSceneFolder::PostEditUndo(TSharedPtr<ITransactionObjectAnnotation> TransactionAnnotation)
{
Super::PostEditUndo(TransactionAnnotation);
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnPostUndo);
}
#endif