Files
UnrealEngineUWP/Engine/Source/Runtime/MovieScene/Private/MovieSceneFolder.cpp
Max Chen 0167d4ea0f Sequencer: MVVM2 branch and Layer Bars
Copying //Tasks/UE5/Dev-SequencerMVVM2 to Main (//UE5/Main) @20364093

#preflight 628866dfb94f739b152c1e29
#preflight 628866e4585e8f793ee80943
#rb ludovic.chabant, andrew.rodham
#fyi ludovic.chabant, andrew.rodham, andrew.porter
#jira UE-105322

[CL 20364493 by Max Chen in ue5-main branch]
2022-05-25 10:39:33 -04:00

399 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MovieSceneFolder.h"
#include "MovieScene.h"
#include "MovieSceneTrack.h"
#include "Algo/Count.h"
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);
}
}
#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::GetChildMasterTracks() const
{
return ChildMasterTracks;
}
void UMovieSceneFolder::AddChildMasterTrack( UMovieSceneTrack* InMasterTrack )
{
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->RemoveChildMasterTrack(InMasterTrack);
}
}
#endif
ChildMasterTracks.Add( InMasterTrack );
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnTrackAdded, InMasterTrack);
}
void UMovieSceneFolder::RemoveChildMasterTrack( UMovieSceneTrack* InMasterTrack )
{
Modify();
if (ChildMasterTracks.Remove( InMasterTrack ) > 0)
{
EventHandlers.Trigger(&UE::MovieScene::IFolderEventHandler::OnTrackRemoved, InMasterTrack);
}
}
void UMovieSceneFolder::ClearChildMasterTracks()
{
Modify();
ChildMasterTracks.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 Master Tracks
for(int32 ChildMasterTrackIndex = 0; ChildMasterTrackIndex < ChildMasterTracks.Num(); ChildMasterTrackIndex++)
{
const UMovieSceneTrack* ChildTrack = ChildMasterTracks[ChildMasterTrackIndex];
if (!OwningScene->GetMasterTracks().Contains(ChildTrack))
{
ChildMasterTracks.RemoveAt(ChildMasterTrackIndex);
ChildMasterTrackIndex--;
UE_LOG(LogMovieScene, Warning, TEXT("Folder (%s) in Sequence (%s) contained a reference to a Master 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))
{
ChildObjectBindings.RemoveAt(ChildObjectBindingIndex);
ChildObjectBindingIndex--;
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());
}
}
// 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;
}
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 );
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