Files
UnrealEngine/Engine/Source/Editor/Sequencer/Private/SequencerClipboardReconciler.cpp
2025-05-18 13:04:45 +08:00

243 lines
6.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SequencerClipboardReconciler.h"
#include "HAL/PlatformCrt.h"
#include "IKeyArea.h"
#include "MVVM/ViewModels/ChannelModel.h"
#include "Templates/Tuple.h"
#include "Templates/UnrealTemplate.h"
class UMovieSceneSection;
TMap<FName, TArray<FName>> FSequencerClipboardReconciler::KeyAreaAliases;
FSequencerClipboardReconciler::FSequencerClipboardReconciler(TSharedRef<const FMovieSceneClipboard> InClipboard)
: Clipboard(MoveTemp(InClipboard))
, bCanAutoPaste(false)
{
}
bool FSequencerClipboardReconciler::Paste(const FSequencerPasteEnvironment& PasteEnvironment)
{
if (PasteDestination.Num() == 0)
{
return false;
}
if (Reconcile())
{
return PasteImpl(PasteEnvironment);
}
return false;
}
bool FSequencerClipboardReconciler::Reconcile()
{
if (!bReconcileResult.IsSet())
{
if (PasteDestination.Num() == 0)
{
bReconcileResult = false;
}
else if (Clipboard->GetKeyTrackGroups().Num() == 1)
{
bReconcileResult = ReconcileOneToMany();
}
else
{
bReconcileResult = false;
}
}
return bReconcileResult.GetValue();
}
FSequencerClipboardPasteGroup FSequencerClipboardReconciler::AddDestinationGroup()
{
return FSequencerClipboardPasteGroup(PasteDestination);
}
void FSequencerClipboardReconciler::AddTrackAlias(FName TargetName, FName Alias)
{
KeyAreaAliases.FindOrAdd(TargetName).Add(Alias);
KeyAreaAliases.FindOrAdd(Alias).Add(TargetName);
}
bool FSequencerClipboardReconciler::CanAutoPaste() const
{
return bCanAutoPaste;
}
bool FSequencerClipboardReconciler::PasteImpl(const FSequencerPasteEnvironment& PasteEnvironment)
{
bool bAnythingPasted = false;
for (auto& MetaDataPair : MetaData)
{
const TArray<FMovieSceneClipboardKeyTrack>& SrcArray = Clipboard->GetKeyTrackGroups()[MetaDataPair.Value.SourceGroup];
TArray<TSharedPtr<UE::Sequencer::FChannelModel>>& DstArray = PasteDestination[MetaDataPair.Key];
int32 SrcIndex = 0;
int32 DstIndex = 0;
FPasteMetaData::EMethod Method = MetaDataPair.Value.Method;
if (Method == FPasteMetaData::Custom)
{
for (auto& IndexPair : MetaDataPair.Value.DestToSrcMap)
{
if (UMovieSceneSection* Section = DstArray[DstIndex]->GetSection())
{
TArray<FKeyHandle> PastedKeys = DstArray[IndexPair.Key]->GetKeyArea()->PasteKeys(SrcArray[IndexPair.Value], Clipboard->GetEnvironment(), PasteEnvironment);
for (FKeyHandle KeyHandle : PastedKeys)
{
PasteEnvironment.ReportPastedKey(KeyHandle, DstArray[IndexPair.Key]);
}
bAnythingPasted = true;
}
}
}
else for (; SrcIndex < SrcArray.Num() && DstIndex < DstArray.Num();)
{
if (UMovieSceneSection* Section = DstArray[DstIndex]->GetSection())
{
TArray<FKeyHandle> PastedKeys = DstArray[DstIndex]->GetKeyArea()->PasteKeys(SrcArray[SrcIndex], Clipboard->GetEnvironment(), PasteEnvironment);
for (FKeyHandle KeyHandle : PastedKeys)
{
PasteEnvironment.ReportPastedKey(KeyHandle, DstArray[DstIndex]);
}
bAnythingPasted = true;
}
switch (Method)
{
case FPasteMetaData::Compress:
++SrcIndex;
break;
case FPasteMetaData::Expand:
++DstIndex;
break;
case FPasteMetaData::Apply:
++SrcIndex;
++DstIndex;
break;
case FPasteMetaData::ApplyRepeating:
++SrcIndex;
++DstIndex;
if (SrcIndex >= SrcArray.Num())
{
// Wrap around source index
SrcIndex = 0;
}
break;
}
}
}
return bAnythingPasted;
}
bool FSequencerClipboardReconciler::FindMatchingGroup(const TArray<TSharedPtr<UE::Sequencer::FChannelModel>>& Destination, const TArray<FMovieSceneClipboardKeyTrack>& Source, TMap<int32, int32>& Map, bool bAllowAliases)
{
// If we find an exact match, we use custom
bool bFoundMatch = false;
for (int32 DstIndex = 0; DstIndex < Destination.Num(); ++DstIndex)
{
FName DstName = Destination[DstIndex]->GetKeyArea()->GetName();
const int32 SourceIndex = Source.IndexOfByPredicate([&](const FMovieSceneClipboardKeyTrack& Track){
FName SrcName = Track.GetName();
if (SrcName == DstName)
{
return true;
}
if (const TArray<FName>* CustomRules = KeyAreaAliases.Find(DstName))
{
return CustomRules->ContainsByPredicate([&](const FName& Match){
return SrcName == Match;
});
}
return false;
});
if (SourceIndex != INDEX_NONE)
{
Map.Add(DstIndex, SourceIndex);
bFoundMatch = true;
}
}
return bFoundMatch;
}
bool FSequencerClipboardReconciler::ReconcileOneToMany()
{
const TArray<FMovieSceneClipboardKeyTrack>& Source = Clipboard->GetKeyTrackGroups()[0];
const int32 NumSourceTracks = Source.Num();
// We have one group of tracks, and are pasting into one or more groups of tracks
for (int32 Index = 0; Index < PasteDestination.Num(); ++Index)
{
FPasteMetaData& ThisMetaData = MetaData.Add(Index, FPasteMetaData(0, FPasteMetaData::Apply));
const int32 NumDestTracks = PasteDestination[Index].Num();
// Precedence list:
// 1. Find an exact name match in any destination
// 2. Expand single source tracks to multiple destination tracks
// 3. Compress multiple source tracks to a single destination track
// 4. Find ay alias for the source tracks by name
// 5. Blindly copy the source tracks by order if they are numerically equal to the destination
// 6. Blindly copy the source tracks in a repeating way across the destination tracks, if dest is a multiple of src
// 7. Bail - we can't make any more reasonable assumptions about what the user expects
if (FindMatchingGroup(PasteDestination[Index], Source, ThisMetaData.DestToSrcMap, false /*bAllowAliases*/))
{
bCanAutoPaste = true;
ThisMetaData.Method = FPasteMetaData::Custom;
}
else if (NumSourceTracks == 1 && NumDestTracks != 1)
{
// If we're pasting a single track, paste it into all destination areas
ThisMetaData.Method = FPasteMetaData::Expand;
}
else if (NumDestTracks == 1 && NumSourceTracks != 1)
{
// If we're pasting multiple into a single track, compress them toghether
ThisMetaData.Method = FPasteMetaData::Compress;
}
else if (FindMatchingGroup(PasteDestination[Index], Source, ThisMetaData.DestToSrcMap, true /*bAllowAliases*/))
{
ThisMetaData.Method = FPasteMetaData::Custom;
}
else if (NumSourceTracks == NumDestTracks)
{
// If they have the same number of tracks, just apply directly
ThisMetaData.Method = FPasteMetaData::Apply;
bCanAutoPaste = true;
}
else if (NumDestTracks % NumSourceTracks == 0)
{
// If we're into a multiple of the source tracks, apply the selection multiple times
ThisMetaData.Method = FPasteMetaData::ApplyRepeating;
}
else
{
// Incompatible
MetaData.Remove(Index);
}
}
return MetaData.Num() != 0;
}