Files
UnrealEngineUWP/Engine/Source/Editor/Sequencer/Private/Sequencer.cpp
Max Chen b08eaf1051 Sequencer: Don't select folder descendants when retrieving key areas. This ends up selecting all the control rig controls when you select a folder.
#jira UE-154161
#preflight 628f17daf622d972b5b6cc03
#rb mike.zyracki

[CL 20379966 by Max Chen in ue5-main branch]
2022-05-26 11:40:41 -04:00

13774 lines
425 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Sequencer.h"
#include "Engine/EngineTypes.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
#include "Algo/RemoveIf.h"
#include "MVVM/SharedViewModelData.h"
#include "MVVM/ViewModels/SequenceModel.h"
#include "MVVM/ViewModels/SectionModel.h"
#include "MVVM/ViewModels/FolderModel.h"
#include "MVVM/ViewModels/ChannelModel.h"
#include "MVVM/ViewModels/CategoryModel.h"
#include "MVVM/ViewModels/TrackModel.h"
#include "MVVM/ViewModels/TrackRowModel.h"
#include "MVVM/ViewModels/ViewModelIterators.h"
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
#include "MVVM/ViewModels/SequencerOutlinerViewModel.h"
#include "MVVM/ViewModels/SequencerTrackAreaViewModel.h"
#include "MVVM/ViewModels/SequencerEditorViewModel.h"
#include "MVVM/Views/SOutlinerView.h"
#include "MVVM/Views/IOutlinerSelectionHandler.h"
#include "MVVM/Extensions/IDeletableExtension.h"
#include "MVVM/CurveEditorIntegrationExtension.h"
#include "MVVM/ObjectBindingModelStorageExtension.h"
#include "MVVM/SectionModelStorageExtension.h"
#include "Camera/PlayerCameraManager.h"
#include "Misc/MessageDialog.h"
#include "Containers/ArrayBuilder.h"
#include "Misc/FeedbackContext.h"
#include "Misc/ScopedSlowTask.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectIterator.h"
#include "UObject/MetaData.h"
#include "UObject/PropertyPortFlags.h"
#include "Serialization/ArchiveReplaceObjectRef.h"
#include "GameFramework/PlayerController.h"
#include "Engine/Engine.h"
#include "Settings/LevelEditorViewportSettings.h"
#include "Editor.h"
#include "BlueprintActionDatabase.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "MovieScenePossessable.h"
#include "MovieScene.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "Widgets/Layout/SBorder.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "Styling/AppStyle.h"
#include "Exporters/Exporter.h"
#include "Editor/UnrealEdEngine.h"
#include "Camera/CameraActor.h"
#include "Engine/Selection.h"
#include "EngineUtils.h"
#include "LevelEditorViewport.h"
#include "EditorModeManager.h"
#include "UnrealEdMisc.h"
#include "EditorDirectories.h"
#include "FileHelpers.h"
#include "UnrealEdGlobals.h"
#include "SequencerCommands.h"
#include "ISequencerSection.h"
#include "MovieSceneClipboard.h"
#include "SequencerCommonHelpers.h"
#include "SequencerMarkedFrameHelper.h"
#include "SSequencer.h"
#include "SSequencerSection.h"
#include "SequencerKeyCollection.h"
#include "SequencerAddKeyOperation.h"
#include "SequencerSettings.h"
#include "SequencerLog.h"
#include "SequencerEdMode.h"
#include "MovieSceneSequence.h"
#include "MovieSceneFolder.h"
#include "PropertyEditorModule.h"
#include "EditorWidgetsModule.h"
#include "IAssetViewport.h"
#include "EditorSupportDelegates.h"
#include "MVVM/Views/SOutlinerView.h"
#include "ScopedTransaction.h"
#include "Tracks/MovieScene3DTransformTrack.h"
#include "Tracks/MovieScene3DAttachTrack.h"
#include "Tracks/MovieSceneCameraAnimTrack.h"
#include "Tracks/MovieSceneCameraShakeTrack.h"
#include "Tracks/MovieSceneCameraCutTrack.h"
#include "ISequencerTrackEditor.h"
#include "MovieSceneToolHelpers.h"
#include "Sections/MovieScene3DAttachSection.h"
#include "Sections/MovieSceneBoolSection.h"
#include "Sections/MovieSceneCameraCutSection.h"
#include "Sections/MovieScene3DTransformSection.h"
#include "Sections/MovieSceneSpawnSection.h"
#include "Sections/MovieSceneSubSection.h"
#include "Tracks/MovieSceneSubTrack.h"
#include "Sections/MovieSceneCinematicShotSection.h"
#include "MovieSceneObjectBindingIDCustomization.h"
#include "MovieSceneObjectBindingID.h"
#include "ISettingsModule.h"
#include "Framework/Commands/GenericCommands.h"
#include "Tracks/MovieSceneSpawnTrack.h"
#include "Tracks/MovieScenePropertyTrack.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/Input/STextEntryPopup.h"
#include "SequencerHotspots.h"
#include "MovieSceneCaptureDialogModule.h"
#include "AutomatedLevelSequenceCapture.h"
#include "MovieSceneCommonHelpers.h"
#include "SceneOutlinerModule.h"
#include "SceneOutlinerPublicTypes.h"
#include "IContentBrowserSingleton.h"
#include "ContentBrowserModule.h"
#include "PackageTools.h"
#include "SequencerUtilities.h"
#include "Tracks/MovieSceneCinematicShotTrack.h"
#include "CineCameraActor.h"
#include "CameraRig_Rail.h"
#include "CameraRig_Crane.h"
#include "Components/SplineComponent.h"
#include "DesktopPlatformModule.h"
#include "Factories.h"
#include "FbxExporter.h"
#include "ObjectBindingTagCache.h"
#include "UnrealExporter.h"
#include "ISequencerEditorObjectBinding.h"
#include "LevelSequence.h"
#include "LevelSequenceActor.h"
#include "IVREditorModule.h"
#include "HAL/PlatformApplicationMisc.h"
#include "SequencerKeyActor.h"
#include "MovieSceneCopyableBinding.h"
#include "MovieSceneCopyableTrack.h"
#include "ISequencerChannelInterface.h"
#include "IMovieRendererInterface.h"
#include "SequencerKeyCollection.h"
#include "CurveEditor.h"
#include "CurveEditorScreenSpace.h"
#include "CurveDataAbstraction.h"
#include "Fonts/FontMeasure.h"
#include "MovieSceneTimeHelpers.h"
#include "FrameNumberNumericInterface.h"
#include "UObject/StrongObjectPtr.h"
#include "SequencerExportTask.h"
#include "LevelUtils.h"
#include "Engine/Blueprint.h"
#include "MovieSceneSequenceEditor.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "ISerializedRecorder.h"
#include "Features/IModularFeatures.h"
#include "SequencerContextMenus.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "EngineAnalytics.h"
#include "Interfaces/IAnalyticsProvider.h"
#include "Components/SkeletalMeshComponent.h"
#include "EntitySystem/MovieSceneInitialValueCache.h"
#include "SequencerCustomizationManager.h"
#include "SSequencerGroupManager.h"
#include "ActorTreeItem.h"
#include "Widgets/Layout/SSpacer.h"
#include "CurveEditorCommands.h"
#include "Tools/SequencerEditTool_Movement.h"
#include "Tools/SequencerEditTool_Selection.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieScenePreAnimatedStateSystem.h"
#include "Systems/MovieSceneMotionVectorSimulationSystem.h"
#include "EngineModule.h"
#define LOCTEXT_NAMESPACE "Sequencer"
DEFINE_LOG_CATEGORY(LogSequencer);
static TAutoConsoleVariable<float> CVarAutoScrubSpeed(
TEXT("Sequencer.AutoScrubSpeed"),
6.0f,
TEXT("How fast to scrub forward/backward when auto-scrubbing"));
static TAutoConsoleVariable<float> CVarAutoScrubCurveExponent(
TEXT("Sequencer.AutoScrubCurveExponent"),
2.0f,
TEXT("How much to ramp in and out the scrub speed when auto-scrubbing"));
struct FSequencerCurveEditorBounds : ICurveEditorBounds
{
FSequencerCurveEditorBounds(TSharedRef<FSequencer> InSequencer)
: WeakSequencer(InSequencer)
{
TRange<double> Bounds = InSequencer->GetViewRange();
InputMin = Bounds.GetLowerBoundValue();
InputMax = Bounds.GetUpperBoundValue();
}
virtual void GetInputBounds(double& OutMin, double& OutMax) const override
{
TSharedPtr<FSequencer> Sequencer = WeakSequencer.Pin();
if (Sequencer.IsValid())
{
const bool bLinkTimeRange = Sequencer->GetSequencerSettings()->GetLinkCurveEditorTimeRange();
if (bLinkTimeRange)
{
TRange<double> Bounds = Sequencer->GetViewRange();
OutMin = Bounds.GetLowerBoundValue();
OutMax = Bounds.GetUpperBoundValue();
}
else
{
// If they don't want to link the time range with Sequencer we return the cached value.
OutMin = InputMin;
OutMax = InputMax;
}
}
}
virtual void SetInputBounds(double InMin, double InMax) override
{
TSharedPtr<FSequencer> Sequencer = WeakSequencer.Pin();
if (Sequencer.IsValid())
{
const bool bLinkTimeRange = Sequencer->GetSequencerSettings()->GetLinkCurveEditorTimeRange();
if (bLinkTimeRange)
{
FFrameRate TickResolution = Sequencer->GetFocusedTickResolution();
if (InMin * TickResolution > TNumericLimits<int32>::Lowest() && InMax * TickResolution < TNumericLimits<int32>::Max())
{
Sequencer->SetViewRange(TRange<double>(InMin, InMax), EViewRangeInterpolation::Immediate);
}
}
// We update these even if you are linked to the Sequencer Timeline so that when you turn off the link setting
// you don't pop to your last values, instead your view stays as is and just stops moving when Sequencer moves.
InputMin = InMin;
InputMax = InMax;
}
}
/** The min/max values for the viewing range. Only used if Curve Editor/Sequencer aren't linked ranges. */
double InputMin, InputMax;
TWeakPtr<FSequencer> WeakSequencer;
};
class FSequencerOutlinerSelectionHandler :
public UE::Sequencer::IOutlinerSelectionHandler
{
public:
bool bForwardSelectionToView;
/** True if we are waiting for menus to be closed to synchronize external selection. */
bool bPendingSelectionChangedNotification;
FSequencerOutlinerSelectionHandler(FSequencerSelection& InSelection)
: Selection(InSelection)
{
bForwardSelectionToView = true;
bPendingSelectionChangedNotification = false;
Selection.GetOnOutlinerNodeSelectionChanged().AddRaw(this, &FSequencerOutlinerSelectionHandler::OnSelectedOutlinerNodesChanged);
}
void SetTreeViews(TSharedPtr<UE::Sequencer::SOutlinerView> InTreeView, TSharedPtr<UE::Sequencer::SOutlinerView> InPinnedTreeView)
{
WeakTreeView = InTreeView;
WeakPinnedTreeView = InPinnedTreeView;
}
void OnSelectedOutlinerNodesChanged()
{
using namespace UE::Sequencer;
if (bForwardSelectionToView)
{
TGuardValue<bool> Guard(bForwardSelectionToView, false);
// Called when our selection has changed programatically from code
TSharedPtr<SOutlinerView> TreeView = WeakTreeView.Pin();
if (ensure(TreeView))
{
TreeView->ForceSetSelectedItems(Selection.GetSelectedOutlinerItems());
}
TSharedPtr<SOutlinerView> PinnedTreeView = WeakPinnedTreeView.Pin();
if (ensure(PinnedTreeView))
{
PinnedTreeView->ForceSetSelectedItems(Selection.GetSelectedOutlinerItems());
}
}
}
void SelectOutlinerItems(const TArrayView<UE::Sequencer::TWeakViewModelPtr<UE::Sequencer::IOutlinerExtension>>& Items, bool bRightMouseButtonDown) override
{
using namespace UE::Sequencer;
TGuardValue<bool> Guard(bForwardSelectionToView, false);
Selection.SuspendBroadcast();
Selection.EmptySelectedOutlinerNodes();
for (const TWeakViewModelPtr<IOutlinerExtension>& Item : Items)
{
if (TViewModelPtr<IOutlinerExtension> ItemPtr = Item.Pin())
{
Selection.AddToOutlinerSelection(ItemPtr);
}
}
Selection.ResumeBroadcast();
if (bRightMouseButtonDown)
{
bPendingSelectionChangedNotification = true;
}
else
{
Selection.GetOnOutlinerNodeSelectionChanged().Broadcast();
}
}
private:
FSequencerSelection& Selection;
TWeakPtr<UE::Sequencer::SOutlinerView> WeakTreeView;
TWeakPtr<UE::Sequencer::SOutlinerView> WeakPinnedTreeView;
};
class FSequencerCurveEditor : public FCurveEditor
{
public:
TWeakPtr<FSequencer> WeakSequencer;
FSequencerCurveEditor(TWeakPtr<FSequencer> InSequencer)
: WeakSequencer(InSequencer)
{}
virtual void GetGridLinesX(TArray<float>& MajorGridLines, TArray<float>& MinorGridLines, TArray<FText>* MajorGridLabels) const override
{
TSharedPtr<FSequencer> Sequencer = WeakSequencer.Pin();
FCurveEditorScreenSpaceH PanelInputSpace = GetPanelInputSpace();
double MajorGridStep = 0.0;
int32 MinorDivisions = 0;
if (Sequencer.IsValid() && Sequencer->GetGridMetrics(PanelInputSpace.GetPhysicalWidth(), PanelInputSpace.GetInputMin(), PanelInputSpace.GetInputMax(), MajorGridStep, MinorDivisions))
{
const double FirstMajorLine = FMath::FloorToDouble(PanelInputSpace.GetInputMin() / MajorGridStep) * MajorGridStep;
const double LastMajorLine = FMath::CeilToDouble(PanelInputSpace.GetInputMax() / MajorGridStep) * MajorGridStep;
for (double CurrentMajorLine = FirstMajorLine; CurrentMajorLine < LastMajorLine; CurrentMajorLine += MajorGridStep)
{
MajorGridLines.Add( PanelInputSpace.SecondsToScreen(CurrentMajorLine) );
for (int32 Step = 1; Step < MinorDivisions; ++Step)
{
MinorGridLines.Add( PanelInputSpace.SecondsToScreen(CurrentMajorLine + Step*MajorGridStep/MinorDivisions) );
}
}
}
}
};
void FSequencer::InitSequencer(const FSequencerInitParams& InitParams, const TSharedRef<ISequencerObjectChangeListener>& InObjectChangeListener, const TArray<FOnCreateTrackEditor>& TrackEditorDelegates, const TArray<FOnCreateEditorObjectBinding>& EditorObjectBindingDelegates)
{
using namespace UE::Sequencer;
bIsEditingWithinLevelEditor = InitParams.bEditWithinLevelEditor;
ScrubStyle = InitParams.ViewParams.ScrubberStyle;
HostCapabilities = InitParams.HostCapabilities;
SilentModeCount = 0;
bReadOnly = InitParams.ViewParams.bReadOnly;
GetPlaybackSpeeds = InitParams.ViewParams.OnGetPlaybackSpeeds;
const int32 IndexOfOne = GetPlaybackSpeeds.Execute().Find(1.f);
check(IndexOfOne != INDEX_NONE);
CurrentSpeedIndex = IndexOfOne;
if (InitParams.SpawnRegister.IsValid())
{
SpawnRegister = InitParams.SpawnRegister;
}
else
{
// Spawnables not supported
SpawnRegister = MakeShareable(new FNullMovieSceneSpawnRegister);
}
EventContextsAttribute = InitParams.EventContexts;
if (EventContextsAttribute.IsSet())
{
CachedEventContexts.Reset();
for (UObject* Object : EventContextsAttribute.Get())
{
CachedEventContexts.Add(Object);
}
}
PlaybackContextAttribute = InitParams.PlaybackContext;
CachedPlaybackContext = PlaybackContextAttribute.Get(nullptr);
PlaybackClientAttribute = InitParams.PlaybackClient;
CachedPlaybackClient = TWeakInterfacePtr<IMovieScenePlaybackClient>(PlaybackClientAttribute.Get(nullptr));
Settings = USequencerSettingsContainer::GetOrCreate<USequencerSettings>(*InitParams.ViewParams.UniqueName);
Settings->GetOnEvaluateSubSequencesInIsolationChanged().AddSP(this, &FSequencer::RestorePreAnimatedState);
Settings->GetOnShowSelectedNodesOnlyChanged().AddSP(this, &FSequencer::OnSelectedNodesOnlyChanged);
ObjectBindingTagCache = MakeUnique<FObjectBindingTagCache>();
FCurveEditorInitParams CurveEditorInitParams;
{
}
{
CurveEditorModel = MakeShared<FSequencerCurveEditor>(SharedThis(this));
CurveEditorModel->SetBounds(MakeUnique<FSequencerCurveEditorBounds>(SharedThis(this)));
CurveEditorModel->InitCurveEditor(CurveEditorInitParams);
CurveEditorModel->InputSnapEnabledAttribute = MakeAttributeLambda([this]{ return Settings->GetIsSnapEnabled(); });
CurveEditorModel->OnInputSnapEnabledChanged = FOnSetBoolean::CreateLambda([this](bool NewValue){ Settings->SetIsSnapEnabled(NewValue); });
CurveEditorModel->OutputSnapEnabledAttribute = MakeAttributeLambda([this]{ return Settings->GetSnapCurveValueToInterval(); });
CurveEditorModel->OnOutputSnapEnabledChanged = FOnSetBoolean::CreateLambda([this](bool NewValue){ Settings->SetSnapCurveValueToInterval(NewValue); });
CurveEditorModel->FixedGridSpacingAttribute = MakeAttributeLambda([this]() -> TOptional<float> { return Settings->GetGridSpacing(); });
CurveEditorModel->InputSnapRateAttribute = MakeAttributeSP(this, &FSequencer::GetFocusedDisplayRate);
CurveEditorModel->DefaultKeyAttributes = MakeAttributeSP(this, &FSequencer::GetDefaultKeyAttributes);
CurveEditorModel->OnCurveArrayChanged.AddRaw(this, &FSequencer::OnCurveModelDisplayChanged);
}
{
FDelegateHandle OnBlueprintPreCompileHandle = GEditor->OnBlueprintPreCompile().AddLambda([&](UBlueprint* InBlueprint)
{
// Restore pre animate state since objects will be reinstanced and current cached state will no longer be valid.
if (InBlueprint && InBlueprint->GeneratedClass.Get())
{
PreAnimatedState.RestorePreAnimatedState(InBlueprint->GeneratedClass.Get());
}
});
AcquiredResources.Add([=] { GEditor->OnBlueprintPreCompile().Remove(OnBlueprintPreCompileHandle); });
FDelegateHandle OnBlueprintCompiledHandle = GEditor->OnBlueprintCompiled().AddLambda([&]
{
State.InvalidateExpiredObjects();
// Force re-evaluation since animated state was restored in PreCompile
bNeedsEvaluate = true;
});
AcquiredResources.Add([=] { GEditor->OnBlueprintCompiled().Remove(OnBlueprintCompiledHandle); });
}
{
FDelegateHandle OnObjectsReplacedHandle = FCoreUObjectDelegates::OnObjectsReplaced.AddLambda([&](const TMap<UObject*, UObject*>& ReplacementMap)
{
// Close sequencer if any of the objects being replaced is itself
TArray<UPackage*> AllSequences;
if (UMovieSceneSequence* Sequence = RootSequence.Get())
{
if (UPackage* Package = Sequence->GetOutermost())
{
AllSequences.AddUnique(Package);
}
}
FMovieSceneCompiledDataID DataID = CompiledDataManager->GetDataID(RootSequence.Get());
if (const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(DataID))
{
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy->AllSubSequenceData())
{
if (UMovieSceneSequence* Sequence = Pair.Value.GetSequence())
{
if (UPackage* Package = Sequence->GetOutermost())
{
AllSequences.AddUnique(Package);
}
}
}
}
for (TPair<UObject*, UObject*> ReplacedObject : ReplacementMap)
{
if (AllSequences.Contains(ReplacedObject.Value) || AllSequences.Contains(ReplacedObject.Key))
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllEditorsForAsset(GetRootMovieSceneSequence());
return;
}
}
//Reset Bindings for replaced objects.
for (TPair<UObject*, UObject*> ReplacedObject : ReplacementMap)
{
FGuid Guid = GetHandleToObject(ReplacedObject.Key, false);
}
PreAnimatedState.OnObjectsReplaced(ReplacementMap);
});
AcquiredResources.Add([=] { FCoreUObjectDelegates::OnObjectsReplaced.Remove(OnObjectsReplacedHandle); });
}
ToolkitHost = InitParams.ToolkitHost;
PlaybackSpeed = 1.f;
ShuttleMultiplier = 0;
ObjectChangeListener = InObjectChangeListener;
check( ObjectChangeListener.IsValid() );
RootSequence = InitParams.RootSequence;
{
CompiledDataManager = FindObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), TEXT("SequencerCompiledDataManager"));
if (!CompiledDataManager)
{
CompiledDataManager = NewObject<UMovieSceneCompiledDataManager>(GetTransientPackage(), "SequencerCompiledDataManager");
}
}
ActiveTemplateIDs.Add(MovieSceneSequenceID::Root);
ActiveTemplateStates.Add(true);
RootTemplateInstance.Initialize(*InitParams.RootSequence, *this, CompiledDataManager);
RootTemplateInstance.EnableGlobalPreAnimatedStateCapture();
InitialValueCache = UE::MovieScene::FInitialValueCache::GetGlobalInitialValues();
RootTemplateInstance.GetEntitySystemLinker()->AddExtension(InitialValueCache.Get());
// Create tools and bind them to this sequencer
for( int32 DelegateIndex = 0; DelegateIndex < TrackEditorDelegates.Num(); ++DelegateIndex )
{
check( TrackEditorDelegates[DelegateIndex].IsBound() );
// Tools may exist in other modules, call a delegate that will create one for us
TSharedRef<ISequencerTrackEditor> TrackEditor = TrackEditorDelegates[DelegateIndex].Execute( SharedThis( this ) );
if (TrackEditor->SupportsSequence(InitParams.RootSequence))
{
TrackEditors.Add( TrackEditor );
}
}
ViewModel = MakeShared<FSequencerEditorViewModel>(SharedThis(this));
ViewModel->InitializeEditor();
ViewModel->SetSequence(InitParams.RootSequence);
FSequencerOutlinerViewModel* OutlinerViewModel = ViewModel->GetOutliner()->CastThisChecked<FSequencerOutlinerViewModel>();
{
OutlinerViewModel->OnGetAddMenuContent = InitParams.ViewParams.OnGetAddMenuContent;
OutlinerViewModel->OnBuildCustomContextMenuForGuid = InitParams.ViewParams.OnBuildCustomContextMenuForGuid;
}
NodeTree->SetRootNode(ViewModel->GetRootModel());
Selection.SetRootModel(ViewModel->GetRootModel());
SelectionHandler = MakeShared<FSequencerOutlinerSelectionHandler>(Selection);
ResetTimeController();
UpdateTimeBases();
PlayPosition.Reset(ConvertFrameTime(GetPlaybackRange().GetLowerBoundValue(), GetRootTickResolution(), PlayPosition.GetInputRate()));
// Make internal widgets
SequencerWidget = SNew( SSequencer, SharedThis( this ) )
.ViewRange( this, &FSequencer::GetViewRange )
.ClampRange( this, &FSequencer::GetClampRange )
.PlaybackRange( this, &FSequencer::GetPlaybackRange )
.PlaybackStatus( this, &FSequencer::GetPlaybackStatus )
.SelectionRange( this, &FSequencer::GetSelectionRange )
.VerticalFrames(this, &FSequencer::GetVerticalFrames)
.MarkedFrames(this, &FSequencer::GetMarkedFrames)
.GlobalMarkedFrames(this, &FSequencer::GetGlobalMarkedFrames)
.OnSetMarkedFrame(this, &FSequencer::SetMarkedFrame)
.OnAddMarkedFrame(this, &FSequencer::AddMarkedFrame)
.OnDeleteMarkedFrame(this, &FSequencer::DeleteMarkedFrame)
.OnDeleteAllMarkedFrames(this, &FSequencer::DeleteAllMarkedFrames )
.SubSequenceRange( this, &FSequencer::GetSubSequenceRange )
.OnPlaybackRangeChanged( this, &FSequencer::SetPlaybackRange )
.OnPlaybackRangeBeginDrag( this, &FSequencer::OnPlaybackRangeBeginDrag )
.OnPlaybackRangeEndDrag( this, &FSequencer::OnPlaybackRangeEndDrag )
.OnSelectionRangeChanged( this, &FSequencer::SetSelectionRange )
.OnSelectionRangeBeginDrag( this, &FSequencer::OnSelectionRangeBeginDrag )
.OnSelectionRangeEndDrag( this, &FSequencer::OnSelectionRangeEndDrag )
.OnMarkBeginDrag(this, &FSequencer::OnMarkBeginDrag)
.OnMarkEndDrag(this, &FSequencer::OnMarkEndDrag)
.IsPlaybackRangeLocked( this, &FSequencer::IsPlaybackRangeLocked )
.OnTogglePlaybackRangeLocked( this, &FSequencer::TogglePlaybackRangeLocked )
.ScrubPosition( this, &FSequencer::GetLocalFrameTime )
.ScrubPositionText( this, &FSequencer::GetFrameTimeText )
.ScrubPositionParent( this, &FSequencer::GetScrubPositionParent)
.ScrubPositionParentChain( this, &FSequencer::GetScrubPositionParentChain)
.OnScrubPositionParentChanged(this, &FSequencer::OnScrubPositionParentChanged)
.OnBeginScrubbing( this, &FSequencer::OnBeginScrubbing )
.OnEndScrubbing( this, &FSequencer::OnEndScrubbing )
.OnScrubPositionChanged( this, &FSequencer::OnScrubPositionChanged )
.OnViewRangeChanged( this, &FSequencer::SetViewRange )
.OnClampRangeChanged( this, &FSequencer::OnClampRangeChanged )
.OnGetNearestKey( this, &FSequencer::OnGetNearestKey )
.OnGetPlaybackSpeeds(InitParams.ViewParams.OnGetPlaybackSpeeds)
.OnReceivedFocus(InitParams.ViewParams.OnReceivedFocus)
.OnInitToolMenuContext(InitParams.ViewParams.OnInitToolMenuContext)
.AddMenuExtender(InitParams.ViewParams.AddMenuExtender)
.ToolbarExtender(InitParams.ViewParams.ToolbarExtender)
.SelectionHandler(SelectionHandler);
SelectionHandler->SetTreeViews(SequencerWidget->GetTreeView(), SequencerWidget->GetPinnedTreeView());
// When undo occurs, get a notification so we can make sure our view is up to date
GEditor->RegisterForUndo(this);
ViewModel->GetTrackArea()->AddEditTool(MakeShared<FSequencerEditTool_Selection>(*this, *SequencerWidget->GetTrackAreaWidget().Get()));
ViewModel->GetTrackArea()->AddEditTool(MakeShared<FSequencerEditTool_Movement>(*this));
for (int32 DelegateIndex = 0; DelegateIndex < EditorObjectBindingDelegates.Num(); ++DelegateIndex)
{
check(EditorObjectBindingDelegates[DelegateIndex].IsBound());
// Object bindings may exist in other modules, call a delegate that will create one for us
TSharedRef<ISequencerEditorObjectBinding> ObjectBinding = EditorObjectBindingDelegates[DelegateIndex].Execute(SharedThis(this));
ObjectBindings.Add(ObjectBinding);
}
FMovieSceneObjectBindingIDCustomization::BindTo(AsShared());
ZoomAnimation = FCurveSequence();
ZoomCurve = ZoomAnimation.AddCurve(0.f, 0.2f, ECurveEaseFunction::QuadIn);
OverlayAnimation = FCurveSequence();
OverlayCurve = OverlayAnimation.AddCurve(0.f, 0.2f, ECurveEaseFunction::QuadIn);
RecordingAnimation = FCurveSequence();
RecordingAnimation.AddCurve(0.f, 1.5f, ECurveEaseFunction::Linear);
// Update initial movie scene data
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::ActiveMovieSceneChanged );
// Update the view range to the new current time
UpdateTimeBoundsToFocusedMovieScene();
// NOTE: Could fill in asset editor commands here!
BindCommands();
// Ensure that the director BP is registered with the action database
if (FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(InitParams.RootSequence))
{
UBlueprint* Blueprint = SequenceEditor->FindDirectorBlueprint(InitParams.RootSequence);
if (Blueprint)
{
if (FBlueprintActionDatabase* Database = FBlueprintActionDatabase::TryGet())
{
Database->RefreshAssetActions(Blueprint);
}
}
}
for (auto TrackEditor : TrackEditors)
{
TrackEditor->OnInitialize();
}
UpdateSequencerCustomizations();
AddNodeGroupsCollectionChangedDelegate();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs[0]);
}
FSequencer::FSequencer()
: SequencerCommandBindings( new FUICommandList )
, SequencerSharedBindings(new FUICommandList)
, CurveEditorSharedBindings(new FUICommandList)
, TargetViewRange(0.f, 5.f)
, LastViewRange(0.f, 5.f)
, ViewRangeBeforeZoom(TRange<double>::Empty())
, PlaybackState( EMovieScenePlayerStatus::Stopped )
, LocalLoopIndexOnBeginScrubbing(FMovieSceneTimeWarping::InvalidWarpCount)
, LocalLoopIndexOffsetDuringScrubbing(0)
, bPerspectiveViewportPossessionEnabled( true )
, bPerspectiveViewportCameraCutEnabled( false )
, bIsEditingWithinLevelEditor( false )
, bNeedTreeRefresh( false )
, NodeTree( MakeShareable( new FSequencerNodeTree( *this ) ) )
, bUpdatingSequencerSelection( false )
, bUpdatingExternalSelection( false )
, bNeedsEvaluate(false)
, bNeedsInvalidateCachedData(false)
, bHasPreAnimatedInfo(false)
{
Selection.GetOnOutlinerNodeSelectionChanged().AddRaw(this, &FSequencer::OnSelectedOutlinerNodesChanged);
Selection.GetOnKeySelectionChanged().AddRaw(this, &FSequencer::OnSelectedOutlinerNodesChanged);
Selection.GetOnOutlinerNodeSelectionChangedObjectGuids().AddRaw(this, &FSequencer::OnSelectedOutlinerNodesChanged);
// Exposes the sequencer and curve editor command lists to subscribers from other systems
FInputBindingManager::Get().RegisterCommandList(FSequencerCommands::Get().GetContextName(), SequencerCommandBindings);
FInputBindingManager::Get().RegisterCommandList(FCurveEditorCommands::Get().GetContextName(), CurveEditorSharedBindings);
}
FSequencer::~FSequencer()
{
RootTemplateInstance.Finish(*this);
if (RootTemplateInstance.GetEntitySystemRunner().IsAttachedToLinker())
{
RootTemplateInstance.GetEntitySystemRunner().Flush();
}
if (GEditor)
{
GEditor->UnregisterForUndo(this);
}
for (auto TrackEditor : TrackEditors)
{
TrackEditor->OnRelease();
}
AcquiredResources.Release();
SelectionHandler.Reset();
ViewModel.Reset();
SequencerWidget.Reset();
TrackEditors.Empty();
}
void FSequencer::Close()
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC != nullptr)
{
LevelVC->ViewModifiers.RemoveAll(this);
}
}
if (OldMaxTickRate.IsSet())
{
GEngine->SetMaxFPS(OldMaxTickRate.GetValue());
OldMaxTickRate.Reset();
}
RootTemplateInstance.Finish(*this);
if (RootTemplateInstance.GetEntitySystemRunner().IsAttachedToLinker())
{
RootTemplateInstance.GetEntitySystemRunner().Flush();
}
RestorePreAnimatedState();
for (auto TrackEditor : TrackEditors)
{
TrackEditor->OnRelease();
}
SequencerWidget.Reset();
TrackEditors.Empty();
GUnrealEd->UpdatePivotLocationForSelection();
// Redraw viewports after restoring pre animated state in case viewports are not set to realtime
GEditor->RedrawLevelEditingViewports();
CachedViewState.RestoreViewState();
OnCloseEventDelegate.Broadcast(AsShared());
}
TSharedPtr<ISequencerTrackEditor> FSequencer::GetTrackEditor(UMovieSceneTrack* InTrack)
{
UClass* TrackClass = InTrack->GetClass();
FObjectKey TrackClassKey(TrackClass);
TSharedPtr<ISequencerTrackEditor> TrackEditor = TrackEditorsByType.FindRef(TrackClassKey);
if (!TrackEditor)
{
for (TSharedPtr<ISequencerTrackEditor> OtherTrackEditor : TrackEditors)
{
if (OtherTrackEditor->SupportsType(TrackClass))
{
TrackEditor = OtherTrackEditor;
TrackEditorsByType.Add(TrackClassKey, TrackEditor);
break;
}
}
}
checkf(TrackEditor, TEXT("Unable to find a track editor for track type %s"), *TrackClass->GetName());
return TrackEditor;
}
void FSequencer::Tick(float InDeltaTime)
{
using namespace UE::Sequencer;
static bool bEnableRefCountCheck = true;
if (!FSlateApplication::Get().AnyMenusVisible())
{
if (SelectionHandler->bPendingSelectionChangedNotification)
{
SelectionHandler->bPendingSelectionChangedNotification = false;
HandleSelectedOutlinerNodesChanged();
}
if (bEnableRefCountCheck)
{
const int32 SequencerRefCount = AsShared().GetSharedReferenceCount() - 1;
ensureAlwaysMsgf(SequencerRefCount == 1, TEXT("Multiple persistent shared references detected for Sequencer. There should only be one persistent authoritative reference. Found %d additional references which will result in FSequencer not being released correctly."), SequencerRefCount - 1);
}
}
TSharedPtr<FSharedViewModelData> SharedModelData = ViewModel->GetRootModel()->GetSharedData();
if (SharedModelData)
{
SharedModelData->ReportLatentHierarchicalOperations();
}
UMovieSceneSignedObject::ResetImplicitScopedModifyDefer();
if (bNeedsInvalidateCachedData)
{
InvalidateCachedData();
bNeedsInvalidateCachedData = false;
}
// Ensure the time bases for our playback position are kept up to date with the root sequence
UpdateTimeBases();
UMovieSceneSequence* RootSequencePtr = RootSequence.Get();
ObjectBindingTagCache->ConditionalUpdate(RootSequencePtr);
Selection.Tick();
UpdateCachedPlaybackContextAndClient();
{
if (CompiledDataManager->IsDirty(RootSequencePtr))
{
CompiledDataManager->Compile(RootSequencePtr);
// Suppress auto evaluation if the sequence signature matches the one to be suppressed
if (!SuppressAutoEvalSignature.IsSet())
{
bNeedsEvaluate = true;
}
else
{
UMovieSceneSequence* SuppressSequence = SuppressAutoEvalSignature->Get<0>().Get();
const FGuid& SuppressSignature = SuppressAutoEvalSignature->Get<1>();
if (!SuppressSequence || SuppressSequence->GetSignature() != SuppressSignature)
{
bNeedsEvaluate = true;
}
}
SuppressAutoEvalSignature.Reset();
}
}
if (bNeedTreeRefresh || NodeTree->NeedsFilterUpdate())
{
EMovieScenePlayerStatus::Type StoredPlaybackState = GetPlaybackStatus();
SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
SelectionPreview.Empty();
RefreshTree();
SetPlaybackStatus(StoredPlaybackState);
}
UObject* PlaybackContext = GetPlaybackContext();
UWorld* World = PlaybackContext ? PlaybackContext->GetWorld() : nullptr;
float Dilation = World ? World->GetWorldSettings()->MatineeTimeDilation : 1.f;
TimeController->Tick(InDeltaTime, PlaybackSpeed * Dilation);
FQualifiedFrameTime GlobalTime = GetGlobalTime();
static const float AutoScrollFactor = 0.1f;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;
// Animate the autoscroll offset if it's set
if (AutoscrollOffset.IsSet())
{
float Offset = AutoscrollOffset.GetValue() * AutoScrollFactor;
SetViewRange(TRange<double>(TargetViewRange.GetLowerBoundValue() + Offset, TargetViewRange.GetUpperBoundValue() + Offset), EViewRangeInterpolation::Immediate);
}
else if (MovieScene)
{
FMovieSceneEditorData& EditorData = MovieScene->GetEditorData();
if (EditorData.GetViewRange() != TargetViewRange)
{
SetViewRange(EditorData.GetViewRange(), EViewRangeInterpolation::Immediate);
}
}
// Animate the autoscrub offset if it's set
if (AutoscrubOffset.IsSet() && PlaybackState == EMovieScenePlayerStatus::Scrubbing )
{
FQualifiedFrameTime CurrentTime = GetLocalTime();
FFrameTime Offset = (AutoscrubOffset.GetValue() * AutoScrollFactor) * CurrentTime.Rate;
SetLocalTimeLooped(CurrentTime.Time + Offset);
}
// Reset to the root sequence if the focused sequence no longer exists. This can happen if either the subsequence has been deleted or the hierarchy has changed.
if (!MovieScene)
{
PopToSequenceInstance(MovieSceneSequenceID::Root);
}
if (GetSelectionRange().IsEmpty() && GetLoopMode() == SLM_LoopSelectionRange)
{
Settings->SetLoopMode(SLM_Loop);
}
if (PlaybackState == EMovieScenePlayerStatus::Playing)
{
FFrameTime NewGlobalTime = TimeController->RequestCurrentTime(GlobalTime, PlaybackSpeed * Dilation, GetFocusedDisplayRate());
// Put the time into local space
SetLocalTimeLooped(NewGlobalTime * RootToLocalTransform);
if (IsAutoScrollEnabled() && GetPlaybackStatus() == EMovieScenePlayerStatus::Playing)
{
const float ThresholdPercentage = 0.15f;
UpdateAutoScroll(GetLocalTime().Time / GetFocusedTickResolution(), ThresholdPercentage);
}
}
else
{
PlayPosition.Reset(GlobalTime.ConvertTo(PlayPosition.GetInputRate()));
}
if (AutoScrubTarget.IsSet())
{
const double ScrubSpeed = CVarAutoScrubSpeed->GetFloat(); // How fast to scrub at peak curve speed
const double AutoScrubExp = CVarAutoScrubCurveExponent->GetFloat(); // How long to ease in and out. Bigger numbers allow for longer easing.
const double SecondsPerFrame = GetFocusedTickResolution().AsInterval() / ScrubSpeed;
const int32 TotalFrames = FMath::Abs(AutoScrubTarget.GetValue().DestinationTime.GetFrame().Value - AutoScrubTarget.GetValue().SourceTime.GetFrame().Value);
const double TargetSeconds = (double)TotalFrames * SecondsPerFrame;
double ElapsedSeconds = FPlatformTime::Seconds() - AutoScrubTarget.GetValue().StartTime;
float Alpha = ElapsedSeconds / TargetSeconds;
Alpha = FMath::Clamp(Alpha, 0.f, 1.f);
int32 NewFrameNumber = FMath::InterpEaseInOut(AutoScrubTarget.GetValue().SourceTime.GetFrame().Value, AutoScrubTarget.GetValue().DestinationTime.GetFrame().Value, Alpha, AutoScrubExp);
FAutoScrubTarget CachedTarget = AutoScrubTarget.GetValue();
SetPlaybackStatus(EMovieScenePlayerStatus::Scrubbing);
PlayPosition.SetTimeBase(GetRootTickResolution(), GetRootTickResolution(), EMovieSceneEvaluationType::WithSubFrames);
SetLocalTimeDirectly(FFrameNumber(NewFrameNumber));
AutoScrubTarget = CachedTarget;
if (FMath::IsNearlyEqual(Alpha, 1.f, KINDA_SMALL_NUMBER))
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
AutoScrubTarget.Reset();
}
}
UpdateSubSequenceData();
// Tick all the tools we own as well
for (int32 EditorIndex = 0; EditorIndex < TrackEditors.Num(); ++EditorIndex)
{
TrackEditors[EditorIndex]->Tick(InDeltaTime);
}
if (!IsInSilentMode())
{
if (bNeedsEvaluate)
{
EvaluateInternal(PlayPosition.GetCurrentPositionAsRange());
}
}
// Reset any player controllers that we were possessing, if we're not possessing them any more
if (!IsPerspectiveViewportCameraCutEnabled() && PrePossessionViewTargets.Num())
{
for (const FCachedViewTarget& CachedView : PrePossessionViewTargets)
{
APlayerController* PlayerController = CachedView.PlayerController.Get();
AActor* ViewTarget = CachedView.ViewTarget.Get();
if (PlayerController && ViewTarget)
{
PlayerController->SetViewTarget(ViewTarget);
}
}
PrePossessionViewTargets.Reset();
}
UpdateCachedCameraActors();
UpdateLevelViewportClientsActorLocks();
if (!bGlobalMarkedFramesCached)
{
UpdateGlobalMarkedFramesCache();
}
}
TSharedRef<SWidget> FSequencer::GetSequencerWidget() const
{
return SequencerWidget.ToSharedRef();
}
UMovieSceneSequence* FSequencer::GetRootMovieSceneSequence() const
{
return RootSequence.Get();
}
FMovieSceneSequenceTransform FSequencer::GetFocusedMovieSceneSequenceTransform() const
{
return RootToLocalTransform;
}
UMovieSceneSequence* FSequencer::GetFocusedMovieSceneSequence() const
{
// the last item is the focused movie scene
if (ActiveTemplateIDs.Num())
{
return RootTemplateInstance.GetSequence(ActiveTemplateIDs.Last());
}
return nullptr;
}
UMovieSceneSubSection* FSequencer::FindSubSection(FMovieSceneSequenceID SequenceID) const
{
if (SequenceID == MovieSceneSequenceID::Root)
{
return nullptr;
}
FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(RootSequence.Get());
const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(DataID);
if (!Hierarchy)
{
return nullptr;
}
const FMovieSceneSequenceHierarchyNode* SequenceNode = Hierarchy->FindNode(SequenceID);
const FMovieSceneSubSequenceData* SubData = Hierarchy->FindSubData(SequenceID);
if (SubData && SequenceNode)
{
UMovieSceneSequence* ParentSequence = RootTemplateInstance.GetSequence(SequenceNode->ParentID);
UMovieScene* ParentMovieScene = ParentSequence ? ParentSequence->GetMovieScene() : nullptr;
if (ParentMovieScene)
{
return FindObject<UMovieSceneSubSection>(ParentMovieScene, *SubData->SectionPath.ToString());
}
}
return nullptr;
}
void FSequencer::ResetToNewRootSequence(UMovieSceneSequence& NewSequence)
{
RemoveNodeGroupsCollectionChangedDelegate();
RootSequence = &NewSequence;
RestorePreAnimatedState();
// Ensure that the director BP is registered with the action database
if (FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(&NewSequence))
{
UBlueprint* Blueprint = SequenceEditor->FindDirectorBlueprint(&NewSequence);
if (Blueprint)
{
if (FBlueprintActionDatabase* Database = FBlueprintActionDatabase::TryGet())
{
Database->RefreshAssetActions(Blueprint);
}
}
}
RootTemplateInstance.Finish(*this);
if (RootTemplateInstance.GetEntitySystemRunner().IsAttachedToLinker())
{
RootTemplateInstance.GetEntitySystemRunner().Flush();
}
ActiveTemplateIDs.Reset();
ActiveTemplateIDs.Add(MovieSceneSequenceID::Root);
ActiveTemplateStates.Reset();
ActiveTemplateStates.Add(true);
RootTemplateInstance.Initialize(NewSequence, *this, CompiledDataManager);
RootToLocalTransform = FMovieSceneSequenceTransform();
RootToLocalLoopCounter = FMovieSceneWarpCounter();
ResetPerMovieSceneData();
SequencerWidget->ResetBreadcrumbs();
PlayPosition.Reset(ConvertFrameTime(GetPlaybackRange().GetLowerBoundValue(), GetRootTickResolution(), PlayPosition.GetInputRate()));
TimeController->Reset(FQualifiedFrameTime(PlayPosition.GetCurrentPosition(), GetRootTickResolution()));
UpdateSequencerCustomizations();
AddNodeGroupsCollectionChangedDelegate();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
}
void FSequencer::FocusSequenceInstance(UMovieSceneSubSection& InSubSection)
{
RemoveNodeGroupsCollectionChangedDelegate();
TemplateIDBackwardStack.Push(ActiveTemplateIDs.Top());
TemplateIDForwardStack.Reset();
UE::MovieScene::FSubSequencePath Path;
// Ensure the hierarchy is up to date
FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(RootSequence.Get());
const FMovieSceneSequenceHierarchy& Hierarchy = CompiledDataManager->GetHierarchyChecked(DataID);
Path.Reset(ActiveTemplateIDs.Last(), &Hierarchy);
// Root out the SequenceID for the sub section
FMovieSceneSequenceID SequenceID = Path.ResolveChildSequenceID(InSubSection.GetSequenceID());
// If the sequence isn't found, reset to the root and dive in from there
if (!Hierarchy.FindSubData(SequenceID))
{
// Pop until the root and reset breadcrumbs
while (MovieSceneSequenceID::Root != ActiveTemplateIDs.Last())
{
ActiveTemplateIDs.Pop();
ActiveTemplateStates.Pop();
}
SequencerWidget->ResetBreadcrumbs();
// Find the requested subsequence's sequence ID
SequenceID = MovieSceneSequenceID::Invalid;
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy.AllSubSequenceData())
{
if (Pair.Value.DeterministicSequenceID == InSubSection.GetSequenceID())
{
SequenceID = Pair.Key;
break;
}
}
// Gather the parent chain's sequence IDs
TArray<FMovieSceneSequenceID> ParentChain;
const FMovieSceneSequenceHierarchyNode* SequenceNode = Hierarchy.FindNode(SequenceID);
FMovieSceneSequenceID ParentID = SequenceNode ? SequenceNode->ParentID : MovieSceneSequenceID::Invalid;
while (ParentID.IsValid() && ParentID != MovieSceneSequenceID::Root)
{
ParentChain.Add(ParentID);
const FMovieSceneSequenceHierarchyNode* ParentNode = Hierarchy.FindNode(ParentID);
ParentID = ParentNode ? ParentNode->ParentID : MovieSceneSequenceID::Invalid;
}
// Push each sequence ID in the parent chain, updating the breadcrumb as we go
for (int32 ParentIDIndex = ParentChain.Num() - 1; ParentIDIndex >= 0; --ParentIDIndex)
{
UMovieSceneSubSection* ParentSubSection = FindSubSection(ParentChain[ParentIDIndex]);
if (ParentSubSection)
{
ActiveTemplateIDs.Push(ParentChain[ParentIDIndex]);
ActiveTemplateStates.Push(ParentSubSection->IsActive());
SequencerWidget->UpdateBreadcrumbs();
}
}
Path.Reset(ActiveTemplateIDs.Last(), &Hierarchy);
// Root out the SequenceID for the sub section
SequenceID = Path.ResolveChildSequenceID(InSubSection.GetSequenceID());
}
if (!ensure(Hierarchy.FindSubData(SequenceID)))
{
return;
}
ActiveTemplateIDs.Push(SequenceID);
ActiveTemplateStates.Push(InSubSection.IsActive());
if (Settings->ShouldEvaluateSubSequencesInIsolation())
{
RestorePreAnimatedState();
UMovieSceneEntitySystemLinker* Linker = RootTemplateInstance.GetEntitySystemLinker();
RootTemplateInstance.FindInstance(MovieSceneSequenceID::Root)->OverrideRootSequence(Linker, ActiveTemplateIDs.Top());
}
UpdateSubSequenceData();
UpdateSequencerCustomizations();
ScrubPositionParent.Reset();
// Reset data that is only used for the previous movie scene
ResetPerMovieSceneData();
SequencerWidget->UpdateBreadcrumbs();
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
if (!State.FindSequence(SequenceID))
{
State.AssignSequence(SequenceID, *FocusedSequence, *this);
}
// Ensure that the director BP is registered with the action database
if (FMovieSceneSequenceEditor* SequenceEditor = FMovieSceneSequenceEditor::Find(FocusedSequence))
{
UBlueprint* Blueprint = SequenceEditor->FindDirectorBlueprint(FocusedSequence);
if (Blueprint)
{
if (FBlueprintActionDatabase* Database = FBlueprintActionDatabase::TryGet())
{
Database->RefreshAssetActions(Blueprint);
}
}
}
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
AddNodeGroupsCollectionChangedDelegate();
bNeedsEvaluate = true;
bGlobalMarkedFramesCached = false;
}
TSharedPtr<UE::Sequencer::FEditorViewModel> FSequencer::GetViewModel() const
{
return ViewModel;
}
void FSequencer::SuppressAutoEvaluation(UMovieSceneSequence* Sequence, const FGuid& InSequenceSignature)
{
SuppressAutoEvalSignature = MakeTuple(MakeWeakObjectPtr(Sequence), InSequenceSignature);
}
FGuid FSequencer::CreateBinding(UObject& InObject, const FString& InName)
{
const FScopedTransaction Transaction(LOCTEXT("CreateBinding", "Create New Binding"));
UMovieSceneSequence* OwnerSequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene();
OwnerSequence->Modify();
OwnerMovieScene->Modify();
const FGuid PossessableGuid = OwnerMovieScene->AddPossessable(InName, InObject.GetClass());
// Attempt to use the parent as a context if necessary
UObject* ParentObject = OwnerSequence->GetParentObject(&InObject);
UObject* BindingContext = GetPlaybackContext(); //UWorld
AActor* ParentActorAdded = nullptr;
FGuid ParentGuid;
if (ParentObject)
{
// Ensure we have possessed the outer object, if necessary
ParentGuid = GetHandleToObject(ParentObject, false);
if (!ParentGuid.IsValid())
{
ParentGuid = GetHandleToObject(ParentObject);
ParentActorAdded = Cast<AActor>(ParentObject);
}
if (OwnerSequence->AreParentContextsSignificant())
{
BindingContext = ParentObject;
}
// Set up parent/child guids for possessables within spawnables
if (ParentGuid.IsValid())
{
FMovieScenePossessable* ChildPossessable = OwnerMovieScene->FindPossessable(PossessableGuid);
if (ensure(ChildPossessable))
{
ChildPossessable->SetParent(ParentGuid, OwnerMovieScene);
}
FMovieSceneSpawnable* ParentSpawnable = OwnerMovieScene->FindSpawnable(ParentGuid);
if (ParentSpawnable)
{
ParentSpawnable->AddChildPossessable(PossessableGuid);
}
}
}
if (!OwnerMovieScene->FindPossessable(PossessableGuid)->BindSpawnableObject(GetFocusedTemplateID(), &InObject, this))
{
OwnerSequence->BindPossessableObject(PossessableGuid, InObject, BindingContext);
}
// Broadcast if a parent actor was added as a result of adding this object
if (ParentActorAdded && ParentGuid.IsValid())
{
OnActorAddedToSequencerEvent.Broadcast(ParentActorAdded, ParentGuid);
}
return PossessableGuid;
}
UObject* FSequencer::GetPlaybackContext() const
{
return CachedPlaybackContext.Get();
}
IMovieScenePlaybackClient* FSequencer::GetPlaybackClient()
{
if (UObject* Obj = CachedPlaybackClient.GetObject())
{
return Cast<IMovieScenePlaybackClient>(Obj);
}
return nullptr;
}
TArray<UObject*> FSequencer::GetEventContexts() const
{
TArray<UObject*> Temp;
CopyFromWeakArray(Temp, CachedEventContexts);
return Temp;
}
void FSequencer::GetKeysFromSelection(TUniquePtr<FSequencerKeyCollection>& KeyCollection, float DuplicateThresholdSeconds)
{
using namespace UE::Sequencer;
if (!KeyCollection.IsValid())
{
KeyCollection.Reset(new FSequencerKeyCollection);
}
TArray<TSharedRef<FViewModel>> SelectedItems;
Selection.GetSelectedOutlinerItems(SelectedItems);
int64 TotalMaxSeconds = static_cast<int64>(TNumericLimits<int32>::Max() / GetFocusedTickResolution().AsDecimal());
FFrameNumber ThresholdFrames = (DuplicateThresholdSeconds * GetFocusedTickResolution()).FloorToFrame();
if (ThresholdFrames.Value < -TotalMaxSeconds)
{
ThresholdFrames.Value = TotalMaxSeconds;
}
else if (ThresholdFrames.Value > TotalMaxSeconds)
{
ThresholdFrames.Value = TotalMaxSeconds;
}
KeyCollection->Update(FSequencerKeyCollectionSignature::FromNodesRecursive(SelectedItems, ThresholdFrames));
}
void FSequencer::GetAllKeys(TUniquePtr<FSequencerKeyCollection>& KeyCollection, float DuplicateThresholdSeconds)
{
using namespace UE::Sequencer;
if (!KeyCollection.IsValid())
{
KeyCollection.Reset(new FSequencerKeyCollection);
}
TArray<TSharedRef<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
FFrameNumber ThresholdFrames = (DuplicateThresholdSeconds * GetFocusedTickResolution()).FloorToFrame();
KeyCollection->Update(FSequencerKeyCollectionSignature::FromNodesRecursive(AllNodes, ThresholdFrames));
}
void FSequencer::PopToSequenceInstance(FMovieSceneSequenceIDRef SequenceID)
{
if( ActiveTemplateIDs.Num() > 1 )
{
TemplateIDBackwardStack.Push(ActiveTemplateIDs.Top());
TemplateIDForwardStack.Reset();
RemoveNodeGroupsCollectionChangedDelegate();
// Pop until we find the movie scene to focus
while( SequenceID != ActiveTemplateIDs.Last() )
{
ActiveTemplateIDs.Pop();
ActiveTemplateStates.Pop();
}
check( ActiveTemplateIDs.Num() > 0 );
UpdateSubSequenceData();
ResetPerMovieSceneData();
if (SequenceID == MovieSceneSequenceID::Root)
{
SequencerWidget->ResetBreadcrumbs();
}
else
{
SequencerWidget->UpdateBreadcrumbs();
}
if (Settings->ShouldEvaluateSubSequencesInIsolation())
{
UMovieSceneEntitySystemLinker* Linker = RootTemplateInstance.GetEntitySystemLinker();
RootTemplateInstance.FindInstance(MovieSceneSequenceID::Root)->OverrideRootSequence(Linker, ActiveTemplateIDs.Top());
}
UpdateSequencerCustomizations();
AddNodeGroupsCollectionChangedDelegate();
ScrubPositionParent.Reset();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
bNeedsEvaluate = true;
bGlobalMarkedFramesCached = false;
}
}
void FSequencer::UpdateSubSequenceData()
{
const bool bIsScrubbing = GetPlaybackStatus() == EMovieScenePlayerStatus::Scrubbing;
const bool bIsSubSequenceWarping = RootToLocalTransform.NestedTransforms.Num() > 0 && RootToLocalTransform.NestedTransforms.Last().IsWarping();
const bool bIsScrubbingWarpingSubSequence = bIsScrubbing && bIsSubSequenceWarping;
SubSequenceRange = TRange<FFrameNumber>::Empty();
RootToLocalTransform = FMovieSceneSequenceTransform();
if (!bIsScrubbingWarpingSubSequence)
{
RootToLocalLoopCounter = FMovieSceneWarpCounter();
}
// else: we're scrubbing, and we don't want to increase/decrease the loop index quite yet,
// because that would mess up time transforms. This would be because the mouse would still be
// before/after the current loop, and therefore would already add/subtract more than a full
// loop's time to the current time, so we don't need the loop counter to change yet.
// Find the parent sub section and set up the sub sequence range, if necessary
if (ActiveTemplateIDs.Num() <= 1)
{
return;
}
const FMovieSceneSequenceHierarchy& Hierarchy = CompiledDataManager->GetHierarchyChecked(RootTemplateInstance.GetCompiledDataID());
const FMovieSceneSubSequenceData* SubSequenceData = Hierarchy.FindSubData(ActiveTemplateIDs.Top());
if (SubSequenceData)
{
SubSequenceRange = SubSequenceData->PlayRange.Value;
RootToLocalTransform = SubSequenceData->RootToSequenceTransform;
const FQualifiedFrameTime RootTime = GetGlobalTime();
if (!bIsScrubbingWarpingSubSequence)
{
FFrameTime LocalTime;
RootToLocalTransform.TransformTime(RootTime.Time, LocalTime, RootToLocalLoopCounter);
}
else
{
// If we are scrubbing _and_ the current sequence is warping, we need to do some custom stuff.
const FFrameNumber PlayRangeSize = SubSequenceData->PlayRange.Value.Size<FFrameNumber>();
const FFrameNumber PlayRangeUpperBound = SubSequenceData->PlayRange.Value.GetUpperBoundValue();
const FFrameNumber PlayRangeLowerBound = SubSequenceData->PlayRange.Value.GetLowerBoundValue();
ensure(LocalLoopIndexOnBeginScrubbing != FMovieSceneTimeWarping::InvalidWarpCount);
ensure(RootToLocalLoopCounter.WarpCounts.Num() > 0);
// Compute the new local time based on the specific loop that we had when we started scrubbing.
FMovieSceneSequenceTransform RootToLocalTransformWithoutLeafLooping = RootToLocalTransform;
FMovieSceneNestedSequenceTransform LeafLooping = RootToLocalTransformWithoutLeafLooping.NestedTransforms.Pop();
FFrameTime LocalTimeWithLastLoopUnwarped = RootTime.Time * RootToLocalTransformWithoutLeafLooping;
LocalTimeWithLastLoopUnwarped = LocalTimeWithLastLoopUnwarped * LeafLooping.LinearTransform;
if (LeafLooping.IsWarping())
{
LeafLooping.Warping.TransformTimeSpecific(
LocalTimeWithLastLoopUnwarped, LocalLoopIndexOnBeginScrubbing, LocalTimeWithLastLoopUnwarped);
}
// Now figure out if we're in a next/previous loop because we scrubbed past the lower/upper bound
// of the loop. Note, again, that we only compute the new loop index for UI display purposes at this
// point (see comment at the beginning of this method). We will commit to the new loop indices
// once we're done scrubbing.
uint32 CurLoopIndex = 0;
while (LocalTimeWithLastLoopUnwarped >= PlayRangeUpperBound)
{
LocalTimeWithLastLoopUnwarped = LocalTimeWithLastLoopUnwarped - PlayRangeSize;
++CurLoopIndex;
}
while (LocalTimeWithLastLoopUnwarped <= PlayRangeLowerBound)
{
LocalTimeWithLastLoopUnwarped = LocalTimeWithLastLoopUnwarped + PlayRangeSize;
--CurLoopIndex;
}
if (CurLoopIndex != LocalLoopIndexOffsetDuringScrubbing)
{
LocalLoopIndexOffsetDuringScrubbing = CurLoopIndex;
// If we jumped to the previous or next loop, we need to invalidate the global marked frames because
// the focused (currently edited) sequence's time transform just changed.
InvalidateGlobalMarkedFramesCache();
}
}
}
}
void FSequencer::UpdateSequencerCustomizations()
{
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>("Sequencer");
TSharedPtr<FSequencerCustomizationManager> Manager = SequencerModule.GetSequencerCustomizationManager();
// Get rid of previously active customizations.
for (const TUniquePtr<ISequencerCustomization>& Customization : ActiveCustomizations)
{
Customization->UnregisterSequencerCustomization();
}
ActiveCustomizations.Reset();
// Get the customizations for the current sequence.
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
check(FocusedSequence != nullptr);
Manager->GetSequencerCustomizations(*FocusedSequence, ActiveCustomizations);
// Get the customization info.
FSequencerCustomizationBuilder Builder(*this, *FocusedSequence);
for (const TUniquePtr<ISequencerCustomization>& Customization : ActiveCustomizations)
{
Customization->RegisterSequencerCustomization(Builder);
}
// Apply customizations to our editor.
SequencerWidget->ApplySequencerCustomizations(Builder.GetCustomizations());
// Apply customizations to ourselves.
OnPaste.Reset();
for (const FSequencerCustomizationInfo& CustomizationInfo : Builder.GetCustomizations())
{
if (CustomizationInfo.OnPaste.IsBound())
{
OnPaste.Add(CustomizationInfo.OnPaste);
}
}
}
void FSequencer::RerunConstructionScripts()
{
TSet<TWeakObjectPtr<AActor> > BoundActors;
FMovieSceneRootEvaluationTemplateInstance& RootTemplate = GetEvaluationTemplate();
UMovieSceneSequence* Sequence = RootTemplate.GetSequence(MovieSceneSequenceID::Root);
if (!Sequence)
{
return;
}
TArray < TPair<FMovieSceneSequenceID, FGuid> > BoundGuids;
GetConstructionScriptActors(Sequence->GetMovieScene(), MovieSceneSequenceID::Root, BoundActors, BoundGuids);
const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(RootTemplateInstance.GetCompiledDataID());
if (Hierarchy)
{
FMovieSceneEvaluationTreeRangeIterator Iter = Hierarchy->GetTree().IterateFromTime(PlayPosition.GetCurrentPosition().FrameNumber);
for (const FMovieSceneSubSequenceTreeEntry& Entry : Hierarchy->GetTree().GetAllData(Iter.Node()))
{
UMovieSceneSequence* SubSequence = Hierarchy->FindSubSequence(Entry.SequenceID);
if (SubSequence)
{
GetConstructionScriptActors(SubSequence->GetMovieScene(), Entry.SequenceID, BoundActors, BoundGuids);
}
}
}
for (TWeakObjectPtr<AActor> BoundActor : BoundActors)
{
if (BoundActor.IsValid())
{
BoundActor.Get()->RerunConstructionScripts();
}
}
for (TPair<FMovieSceneSequenceID, FGuid> BoundGuid : BoundGuids)
{
State.Invalidate(BoundGuid.Value, BoundGuid.Key);
}
}
void FSequencer::GetConstructionScriptActors(UMovieScene* MovieScene, FMovieSceneSequenceIDRef SequenceID, TSet<TWeakObjectPtr<AActor> >& BoundActors, TArray < TPair<FMovieSceneSequenceID, FGuid> >& BoundGuids)
{
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FGuid ThisGuid = MovieScene->GetPossessable(Index).GetGuid();
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(ThisGuid, SequenceID))
{
if (WeakObject.IsValid())
{
AActor* Actor = Cast<AActor>(WeakObject.Get());
if (Actor)
{
UBlueprint* Blueprint = Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy);
if (Blueprint && Blueprint->bRunConstructionScriptInSequencer)
{
BoundActors.Add(Actor);
BoundGuids.Add(TPair<FMovieSceneSequenceID, FGuid>(SequenceID, ThisGuid));
}
}
}
}
}
for (int32 Index = 0; Index < MovieScene->GetSpawnableCount(); ++Index)
{
FGuid ThisGuid = MovieScene->GetSpawnable(Index).GetGuid();
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(ThisGuid, SequenceID))
{
if (WeakObject.IsValid())
{
AActor* Actor = Cast<AActor>(WeakObject.Get());
if (Actor)
{
UBlueprint* Blueprint = Cast<UBlueprint>(Actor->GetClass()->ClassGeneratedBy);
if (Blueprint && Blueprint->bRunConstructionScriptInSequencer)
{
BoundActors.Add(Actor);
BoundGuids.Add(TPair<FMovieSceneSequenceID, FGuid>(SequenceID, ThisGuid));
}
}
}
}
}
}
void FSequencer::DeleteSections(const TSet<TWeakObjectPtr<UMovieSceneSection>>& Sections)
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
bool bAnythingRemoved = false;
FScopedTransaction DeleteSectionTransaction( NSLOCTEXT("Sequencer", "DeleteSection_Transaction", "Delete Section") );
for (const auto& Section : Sections)
{
if (!Section.IsValid() || Section->IsLocked())
{
continue;
}
// if this check fails then the section is outered to a type that doesnt know about the section
UMovieSceneTrack* Track = CastChecked<UMovieSceneTrack>(Section->GetOuter());
{
Track->SetFlags(RF_Transactional);
Track->Modify();
Track->RemoveSection(*Section);
Track->UpdateEasing();
}
bAnythingRemoved = true;
}
if (bAnythingRemoved)
{
// Full refresh required just in case the last section was removed from any track.
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemRemoved );
}
Selection.EmptySelectedTrackAreaItems();
}
void FSequencer::DeleteSelectedKeys()
{
FScopedTransaction DeleteKeysTransaction( NSLOCTEXT("Sequencer", "DeleteSelectedKeys_Transaction", "Delete Selected Keys") );
bool bAnythingRemoved = false;
FSelectedKeysByChannel KeysByChannel(Selection.GetSelectedKeys().Array());
TSet<UMovieSceneSection*> ModifiedSections;
for (const FSelectedChannelInfo& ChannelInfo : KeysByChannel.SelectedChannels)
{
FMovieSceneChannel* Channel = ChannelInfo.Channel.Get();
if (Channel)
{
bool bModified = ModifiedSections.Contains(ChannelInfo.OwningSection);
if (!bModified)
{
bModified = ChannelInfo.OwningSection->TryModify();
}
if (bModified)
{
ModifiedSections.Add(ChannelInfo.OwningSection);
Channel->DeleteKeys(ChannelInfo.KeyHandles);
bAnythingRemoved = true;
}
}
}
if (bAnythingRemoved)
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
Selection.EmptySelectedKeys();
}
}
void FSequencer::SetInterpTangentMode(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode)
{
TArray<FSequencerSelectedKey> SelectedKeysArray = Selection.GetSelectedKeys().Array();
if (SelectedKeysArray.Num() == 0)
{
return;
}
FScopedTransaction SetInterpTangentModeTransaction(NSLOCTEXT("Sequencer", "SetInterpTangentMode_Transaction", "Set Interpolation and Tangent Mode"));
bool bAnythingChanged = false;
FSelectedKeysByChannel KeysByChannel(SelectedKeysArray);
TSet<UMovieSceneSection*> ModifiedSections;
const FName FloatChannelTypeName = FMovieSceneFloatChannel::StaticStruct()->GetFName();
const FName DoubleChannelTypeName = FMovieSceneDoubleChannel::StaticStruct()->GetFName();
// @todo: sequencer-timecode: move this float-specific logic elsewhere to make it extensible for any channel type
for (const FSelectedChannelInfo& ChannelInfo : KeysByChannel.SelectedChannels)
{
FMovieSceneChannel* ChannelPtr = ChannelInfo.Channel.Get();
if (!ChannelPtr)
{
continue;
}
const FName ChannelTypeName = ChannelInfo.Channel.GetChannelTypeName();
if (ChannelTypeName == FloatChannelTypeName || ChannelTypeName == DoubleChannelTypeName)
{
if (!ModifiedSections.Contains(ChannelInfo.OwningSection))
{
ChannelInfo.OwningSection->Modify();
ModifiedSections.Add(ChannelInfo.OwningSection);
}
if (ChannelTypeName == FloatChannelTypeName)
{
FMovieSceneFloatChannel* Channel = static_cast<FMovieSceneFloatChannel*>(ChannelPtr);
TMovieSceneChannelData<FMovieSceneFloatValue> ChannelData = Channel->GetData();
TArrayView<FMovieSceneFloatValue> Values = ChannelData.GetValues();
for (FKeyHandle Handle : ChannelInfo.KeyHandles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE)
{
Values[KeyIndex].InterpMode = InterpMode;
Values[KeyIndex].TangentMode = TangentMode;
bAnythingChanged = true;
}
}
Channel->AutoSetTangents();
}
else if (ChannelTypeName == DoubleChannelTypeName)
{
FMovieSceneDoubleChannel* Channel = static_cast<FMovieSceneDoubleChannel*>(ChannelPtr);
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = Channel->GetData();
TArrayView<FMovieSceneDoubleValue> Values = ChannelData.GetValues();
for (FKeyHandle Handle : ChannelInfo.KeyHandles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE)
{
Values[KeyIndex].InterpMode = InterpMode;
Values[KeyIndex].TangentMode = TangentMode;
bAnythingChanged = true;
}
}
Channel->AutoSetTangents();
}
}
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
}
void FSequencer::ToggleInterpTangentWeightMode()
{
// @todo: sequencer-timecode: move this float-specific logic elsewhere to make it extensible for any channel type
TArray<FSequencerSelectedKey> SelectedKeysArray = Selection.GetSelectedKeys().Array();
if (SelectedKeysArray.Num() == 0)
{
return;
}
FScopedTransaction SetInterpTangentWeightModeTransaction(NSLOCTEXT("Sequencer", "ToggleInterpTangentWeightMode_Transaction", "Toggle Tangent Weight Mode"));
bool bAnythingChanged = false;
FSelectedKeysByChannel KeysByChannel(SelectedKeysArray);
TSet<UMovieSceneSection*> ModifiedSections;
const FName FloatChannelTypeName = FMovieSceneFloatChannel::StaticStruct()->GetFName();
const FName DoubleChannelTypeName = FMovieSceneDoubleChannel::StaticStruct()->GetFName();
// Remove all tangent weights unless we find a compatible key that does not have weights yet
ERichCurveTangentWeightMode WeightModeToApply = RCTWM_WeightedNone;
// First off iterate all the current keys and find any that don't have weights
for (const FSelectedChannelInfo& ChannelInfo : KeysByChannel.SelectedChannels)
{
FMovieSceneChannel* ChannelPtr = ChannelInfo.Channel.Get();
if (!ChannelPtr)
{
continue;
}
if (ChannelInfo.Channel.GetChannelTypeName() == FloatChannelTypeName)
{
FMovieSceneFloatChannel* Channel = static_cast<FMovieSceneFloatChannel*>(ChannelPtr);
TMovieSceneChannelData<FMovieSceneFloatValue> ChannelData = Channel->GetData();
TArrayView<FMovieSceneFloatValue> Values = ChannelData.GetValues();
for (FKeyHandle Handle : ChannelInfo.KeyHandles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE && Values[KeyIndex].InterpMode == RCIM_Cubic && Values[KeyIndex].Tangent.TangentWeightMode == RCTWM_WeightedNone)
{
WeightModeToApply = RCTWM_WeightedBoth;
goto assign_weights;
}
}
}
else if (ChannelInfo.Channel.GetChannelTypeName() == DoubleChannelTypeName)
{
FMovieSceneDoubleChannel* Channel = static_cast<FMovieSceneDoubleChannel*>(ChannelPtr);
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = Channel->GetData();
TArrayView<FMovieSceneDoubleValue> Values = ChannelData.GetValues();
for (FKeyHandle Handle : ChannelInfo.KeyHandles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE && Values[KeyIndex].InterpMode == RCIM_Cubic && Values[KeyIndex].Tangent.TangentWeightMode == RCTWM_WeightedNone)
{
WeightModeToApply = RCTWM_WeightedBoth;
goto assign_weights;
}
}
}
}
assign_weights:
// Assign the new weight mode for all cubic keys
for (const FSelectedChannelInfo& ChannelInfo : KeysByChannel.SelectedChannels)
{
FMovieSceneChannel* ChannelPtr = ChannelInfo.Channel.Get();
if (!ChannelPtr)
{
continue;
}
const FName ChannelTypeName = ChannelInfo.Channel.GetChannelTypeName();
if (ChannelTypeName == FloatChannelTypeName || ChannelTypeName == DoubleChannelTypeName)
{
if (!ModifiedSections.Contains(ChannelInfo.OwningSection))
{
ChannelInfo.OwningSection->Modify();
ModifiedSections.Add(ChannelInfo.OwningSection);
}
if (ChannelTypeName == FloatChannelTypeName)
{
FMovieSceneFloatChannel* Channel = static_cast<FMovieSceneFloatChannel*>(ChannelPtr);
TMovieSceneChannelData<FMovieSceneFloatValue> ChannelData = Channel->GetData();
TArrayView<FMovieSceneFloatValue> Values = ChannelData.GetValues();
for (FKeyHandle Handle : ChannelInfo.KeyHandles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE && Values[KeyIndex].InterpMode == RCIM_Cubic)
{
Values[KeyIndex].Tangent.TangentWeightMode = WeightModeToApply;
bAnythingChanged = true;
}
}
Channel->AutoSetTangents();
}
else if (ChannelTypeName == DoubleChannelTypeName)
{
FMovieSceneDoubleChannel* Channel = static_cast<FMovieSceneDoubleChannel*>(ChannelPtr);
TMovieSceneChannelData<FMovieSceneDoubleValue> ChannelData = Channel->GetData();
TArrayView<FMovieSceneDoubleValue> Values = ChannelData.GetValues();
for (FKeyHandle Handle : ChannelInfo.KeyHandles)
{
const int32 KeyIndex = ChannelData.GetIndex(Handle);
if (KeyIndex != INDEX_NONE && Values[KeyIndex].InterpMode == RCIM_Cubic)
{
Values[KeyIndex].Tangent.TangentWeightMode = WeightModeToApply;
bAnythingChanged = true;
}
}
Channel->AutoSetTangents();
}
}
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
}
void FSequencer::SnapToFrame()
{
FScopedTransaction SnapToFrameTransaction(NSLOCTEXT("Sequencer", "SnapToFrame_Transaction", "Snap Selected Keys to Frame"));
bool bAnythingChanged = false;
FSelectedKeysByChannel KeysByChannel(Selection.GetSelectedKeys().Array());
TSet<UMovieSceneSection*> ModifiedSections;
TArray<FFrameNumber> KeyTimesScratch;
for (const FSelectedChannelInfo& ChannelInfo : KeysByChannel.SelectedChannels)
{
FMovieSceneChannel* Channel = ChannelInfo.Channel.Get();
if (Channel)
{
if (!ModifiedSections.Contains(ChannelInfo.OwningSection))
{
ChannelInfo.OwningSection->Modify();
ModifiedSections.Add(ChannelInfo.OwningSection);
}
const int32 NumKeys = ChannelInfo.KeyHandles.Num();
KeyTimesScratch.Reset(NumKeys);
KeyTimesScratch.SetNum(NumKeys);
Channel->GetKeyTimes(ChannelInfo.KeyHandles, KeyTimesScratch);
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameRate DisplayRate = GetFocusedDisplayRate();
for (FFrameNumber& Time : KeyTimesScratch)
{
// Convert to frame
FFrameNumber PlayFrame = FFrameRate::TransformTime(Time, TickResolution, DisplayRate).RoundToFrame();
FFrameNumber SnappedFrame = FFrameRate::TransformTime(PlayFrame, DisplayRate, TickResolution).RoundToFrame();
Time = SnappedFrame;
}
Channel->SetKeyTimes(ChannelInfo.KeyHandles, KeyTimesScratch);
bAnythingChanged = true;
}
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
}
bool FSequencer::CanSnapToFrame() const
{
const bool bKeysSelected = Selection.GetSelectedKeys().Num() > 0;
return bKeysSelected;
}
void FSequencer::TransformSelectedKeysAndSections(FFrameTime InDeltaTime, float InScale)
{
FScopedTransaction TransformKeysAndSectionsTransaction(NSLOCTEXT("Sequencer", "TransformKeysandSections_Transaction", "Transform Keys and Sections"));
bool bAnythingChanged = false;
TArray<FSequencerSelectedKey> SelectedKeysArray = Selection.GetSelectedKeys().Array();
TArray<TWeakObjectPtr<UMovieSceneSection>> SelectedSectionsArray = Selection.GetSelectedSections().Array();
const FFrameTime OriginTime = GetLocalTime().Time;
FSelectedKeysByChannel KeysByChannel(SelectedKeysArray);
TMap<UMovieSceneSection*, TRange<FFrameNumber>> SectionToNewBounds;
TArray<FFrameNumber> KeyTimesScratch;
if (InScale != 0.f)
{
// Dilate the keys
for (const FSelectedChannelInfo& ChannelInfo : KeysByChannel.SelectedChannels)
{
FMovieSceneChannel* Channel = ChannelInfo.Channel.Get();
if (Channel)
{
// Skip any channels whose section is already selected because they'll be handled below (moving the section and the keys together)
if (SelectedSectionsArray.Contains(ChannelInfo.OwningSection))
{
continue;
}
const int32 NumKeys = ChannelInfo.KeyHandles.Num();
KeyTimesScratch.Reset(NumKeys);
KeyTimesScratch.SetNum(NumKeys);
// Populate the key times scratch buffer with the times for these handles
Channel->GetKeyTimes(ChannelInfo.KeyHandles, KeyTimesScratch);
// We have to find the lowest key time and the highest key time. They're added based on selection order so we can't rely on their order in the array.
FFrameTime LowestFrameTime = KeyTimesScratch[0];
FFrameTime HighestFrameTime = KeyTimesScratch[0];
// Perform the transformation
for (FFrameNumber& Time : KeyTimesScratch)
{
FFrameTime KeyTime = Time;
Time = (OriginTime + InDeltaTime + (KeyTime - OriginTime) * InScale).FloorToFrame();
if (Time < LowestFrameTime)
{
LowestFrameTime = Time;
}
if (Time > HighestFrameTime)
{
HighestFrameTime = Time;
}
}
TRange<FFrameNumber>* NewSectionBounds = SectionToNewBounds.Find(ChannelInfo.OwningSection);
if (!NewSectionBounds)
{
// Call Modify on the owning section before we call SetKeyTimes so that our section bounds/key times stay in sync.
ChannelInfo.OwningSection->Modify();
NewSectionBounds = &SectionToNewBounds.Add(ChannelInfo.OwningSection, ChannelInfo.OwningSection->GetRange());
}
// Expand the range by ensuring the new range contains the range our keys are in. We add one because the highest time is exclusive
// for sections, but HighestFrameTime is measuring only the key's time.
*NewSectionBounds = TRange<FFrameNumber>::Hull(*NewSectionBounds, TRange<FFrameNumber>(LowestFrameTime.GetFrame(), HighestFrameTime.GetFrame() + 1));
// Apply the new, transformed key times
Channel->SetKeyTimes(ChannelInfo.KeyHandles, KeyTimesScratch);
bAnythingChanged = true;
}
}
// Dilate the sections
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : SelectedSectionsArray)
{
UMovieSceneSection* Section = WeakSection.Get();
if (!Section)
{
continue;
}
TRangeBound<FFrameNumber> LowerBound = Section->GetRange().GetLowerBound();
TRangeBound<FFrameNumber> UpperBound = Section->GetRange().GetUpperBound();
if (Section->HasStartFrame())
{
FFrameTime StartTime = Section->GetInclusiveStartFrame();
FFrameNumber StartFrame = (OriginTime + InDeltaTime + (StartTime - OriginTime) * InScale).FloorToFrame();
LowerBound = TRangeBound<FFrameNumber>::Inclusive(StartFrame);
}
if (Section->HasEndFrame())
{
FFrameTime EndTime = Section->GetExclusiveEndFrame();
FFrameNumber EndFrame = (OriginTime + InDeltaTime + (EndTime - OriginTime) * InScale).FloorToFrame();
UpperBound = TRangeBound<FFrameNumber>::Exclusive(EndFrame);
}
TRange<FFrameNumber>* NewSectionBounds = SectionToNewBounds.Find(Section);
if (!NewSectionBounds)
{
// Call Modify on the owning section before we call SetKeyTimes so that our section bounds/key times stay in sync.
Section->Modify();
NewSectionBounds = &SectionToNewBounds.Add( Section, TRange<FFrameNumber>(LowerBound, UpperBound) );
}
// If keys have already modified the section, we're applying the same modification to the section so we can
// overwrite the (possibly) existing bound, so it's okay to just overwrite the range without a TRange::Hull.
*NewSectionBounds = TRange<FFrameNumber>(LowerBound, UpperBound);
bAnythingChanged = true;
// Modify all of the keys of this section
for (const FMovieSceneChannelEntry& Entry : Section->GetChannelProxy().GetAllEntries())
{
for (FMovieSceneChannel* Channel : Entry.GetChannels())
{
TArray<FFrameNumber> KeyTimes;
TArray<FKeyHandle> KeyHandles;
TArray<FFrameNumber> NewKeyTimes;
Channel->GetKeys(TRange<FFrameNumber>::All(), &KeyTimes, &KeyHandles);
for (FFrameNumber KeyTime : KeyTimes)
{
FFrameNumber NewKeyTime = (OriginTime + InDeltaTime + (KeyTime - OriginTime) * InScale).FloorToFrame();
NewKeyTimes.Add(NewKeyTime);
}
Channel->SetKeyTimes(KeyHandles, NewKeyTimes);
}
}
}
}
// Remove any null sections so we don't need a null check inside the loop.
SectionToNewBounds.Remove(nullptr);
for (TTuple<UMovieSceneSection*, TRange<FFrameNumber>>& Pair : SectionToNewBounds)
{
// Set the range of each section that has been modified to their new bounds.
Pair.Key->SetRange(Pair.Value);
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
}
void FSequencer::TranslateSelectedKeysAndSections(bool bTranslateLeft)
{
int32 Shift = bTranslateLeft ? -1 : 1;
FFrameTime Delta = FQualifiedFrameTime(Shift, GetFocusedDisplayRate()).ConvertTo(GetFocusedTickResolution());
TransformSelectedKeysAndSections(Delta, 1.f);
}
void FSequencer::StretchTime(FFrameTime InDeltaTime)
{
// From the current time, find all the keys and sections to the right and move them by InDeltaTime
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction StretchTimeTransaction(NSLOCTEXT("Sequencer", "StretchTime", "Stretch Time"));
TRange<FFrameNumber> CachedSelectionRange = GetSelectionRange();
TRange<FFrameNumber> SelectionRange;
if (InDeltaTime > 0)
{
SelectionRange.SetLowerBound(GetLocalTime().Time.FrameNumber+1);
SelectionRange.SetUpperBound(TRangeBound<FFrameNumber>::Open());
}
else
{
SelectionRange.SetUpperBound(GetLocalTime().Time.FrameNumber-1);
SelectionRange.SetLowerBound(TRangeBound<FFrameNumber>::Open());
}
FocusedMovieScene->SetSelectionRange(SelectionRange);
SelectInSelectionRange(true, true);
TransformSelectedKeysAndSections(InDeltaTime, 1.f);
// Return state
FocusedMovieScene->SetSelectionRange(CachedSelectionRange);
Selection.Empty(); //todo restore key and section selection
}
void FSequencer::ShrinkTime(FFrameTime InDeltaTime)
{
// From the current time, find all the keys and sections to the right and move them by -InDeltaTime
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction StretchTimeTransaction(NSLOCTEXT("Sequencer", "ShrinkTime", "Shrink Time"));
TRange<FFrameNumber> CachedSelectionRange = GetSelectionRange();
// First, check if there's any keys/sections within InDeltaTime
TRange<FFrameNumber> CheckRange;
if (InDeltaTime > 0)
{
CheckRange.SetLowerBound(GetLocalTime().Time.FrameNumber + 1);
CheckRange.SetUpperBound(GetLocalTime().Time.FrameNumber + InDeltaTime.FrameNumber);
}
else
{
CheckRange.SetUpperBound(GetLocalTime().Time.FrameNumber - InDeltaTime.FrameNumber);
CheckRange.SetLowerBound(GetLocalTime().Time.FrameNumber - 1);
}
FocusedMovieScene->SetSelectionRange(CheckRange);
SelectInSelectionRange(true, true);
if (Selection.GetSelectedKeys().Num() > 0)
{
FNotificationInfo Info(FText::Format(NSLOCTEXT("Sequencer", "ShrinkTimeFailedKeys", "Shrink failed. There are {0} keys in between"), Selection.GetSelectedKeys().Num()));
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
// Return state
FocusedMovieScene->SetSelectionRange(CachedSelectionRange);
Selection.Empty(); //todo restore key and section selection
return;
}
if (Selection.GetSelectedSections().Num() > 0)
{
FNotificationInfo Info(FText::Format(NSLOCTEXT("Sequencer", "ShrinkTimeFailedSections", "Shrink failed. There are {0} sections in between"), Selection.GetSelectedSections().Num()));
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
// Return state
FocusedMovieScene->SetSelectionRange(CachedSelectionRange);
Selection.Empty(); //todo restore key and section selection
return;
}
TRange<FFrameNumber> SelectionRange;
if (InDeltaTime > 0)
{
SelectionRange.SetLowerBound(GetLocalTime().Time.FrameNumber + 1);
SelectionRange.SetUpperBound(TRangeBound<FFrameNumber>::Open());
}
else
{
SelectionRange.SetUpperBound(GetLocalTime().Time.FrameNumber - 1);
SelectionRange.SetLowerBound(TRangeBound<FFrameNumber>::Open());
}
FocusedMovieScene->SetSelectionRange(SelectionRange);
SelectInSelectionRange(true, true);
TransformSelectedKeysAndSections(-InDeltaTime, 1.f);
// Return state
FocusedMovieScene->SetSelectionRange(CachedSelectionRange);
Selection.Empty(); //todo restore key and section selection
}
bool FSequencer::CanAddTransformKeysForSelectedObjects() const
{
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
if (TrackEditors[i]->HasTransformKeyBindings() && TrackEditors[i]->CanAddTransformKeysForSelectedObjects())
{
return true;
}
}
return false;
}
void FSequencer::OnAddTransformKeysForSelectedObjects(EMovieSceneTransformChannel Channel)
{
TArray<TSharedPtr<ISequencerTrackEditor>> PossibleTrackEditors;
bool AtLeastOneHasPriority = false;
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
if (TrackEditors[i]->HasTransformKeyBindings() && TrackEditors[i]->CanAddTransformKeysForSelectedObjects())
{
PossibleTrackEditors.Add(TrackEditors[i]);
if (TrackEditors[i]->HasTransformKeyOverridePriority())
{
AtLeastOneHasPriority = true;
}
}
}
for (int32 i = 0; i < PossibleTrackEditors.Num(); ++i)
{
if (AtLeastOneHasPriority)
{
if (PossibleTrackEditors[i]->HasTransformKeyOverridePriority())
{
PossibleTrackEditors[i]->OnAddTransformKeysForSelectedObjects(Channel);
}
}
else
{
PossibleTrackEditors[i]->OnAddTransformKeysForSelectedObjects(Channel);
}
}
}
void FSequencer::OnTogglePilotCamera()
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC != nullptr && LevelVC->AllowsCinematicControl() && LevelVC->GetViewMode() != VMI_Unknown)
{
bool bLockedAny = false;
// If locked to the camera cut track, pilot the camera that the camera cut track is locked to
if (IsPerspectiveViewportCameraCutEnabled())
{
SetPerspectiveViewportCameraCutEnabled(false);
if (LevelVC->GetCinematicActorLock().HasValidLockedActor())
{
LevelVC->SetActorLock(LevelVC->GetCinematicActorLock().GetLockedActor());
LevelVC->SetCinematicActorLock(nullptr);
LevelVC->bLockedCameraView = true;
LevelVC->UpdateViewForLockedActor();
LevelVC->Invalidate();
bLockedAny = true;
}
}
else if (!LevelVC->GetActorLock().HasValidLockedActor())
{
// If NOT piloting, and was previously piloting a camera, start piloting that previous camera
if (LevelVC->GetPreviousActorLock().HasValidLockedActor())
{
LevelVC->SetCinematicActorLock(nullptr);
LevelVC->SetActorLock(LevelVC->GetPreviousActorLock().GetLockedActor());
LevelVC->bLockedCameraView = true;
LevelVC->UpdateViewForLockedActor();
LevelVC->Invalidate();
bLockedAny = true;
}
// If NOT piloting, and was previously locked to the camera cut track, start piloting the camera that the camera cut track was previously locked to
else if (LevelVC->GetPreviousCinematicActorLock().HasValidLockedActor())
{
LevelVC->SetCinematicActorLock(nullptr);
LevelVC->SetActorLock(LevelVC->GetPreviousCinematicActorLock().GetLockedActor());
LevelVC->bLockedCameraView = true;
LevelVC->UpdateViewForLockedActor();
LevelVC->Invalidate();
bLockedAny = true;
}
}
if (!bLockedAny)
{
LevelVC->SetCinematicActorLock(nullptr);
LevelVC->SetActorLock(nullptr);
LevelVC->bLockedCameraView = false;
LevelVC->UpdateViewForLockedActor();
LevelVC->Invalidate();
}
}
}
}
bool FSequencer::IsPilotCamera() const
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC != nullptr && LevelVC->AllowsCinematicControl() && LevelVC->GetViewMode() != VMI_Unknown)
{
if (LevelVC->GetActorLock().HasValidLockedActor())
{
return true;
}
}
}
return false;
}
void FSequencer::OnActorsDropped( const TArray<TWeakObjectPtr<AActor> >& Actors )
{
AddActors(Actors);
}
void FSequencer::NotifyMovieSceneDataChangedInternal()
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::Unknown );
}
void FSequencer::NotifyMovieSceneDataChanged( EMovieSceneDataChangeType DataChangeType )
{
if (!GetFocusedMovieSceneSequence()->GetMovieScene())
{
if (RootSequence.IsValid())
{
ResetToNewRootSequence(*RootSequence.Get());
}
else
{
UE_LOG(LogSequencer, Error, TEXT("Fatal error, focused movie scene no longer valid and there is no root sequence to default to."));
}
}
if (DataChangeType == EMovieSceneDataChangeType::RefreshTree)
{
bNeedTreeRefresh = true;
OnMovieSceneDataChangedDelegate.Broadcast(DataChangeType);
return;
}
else if ( DataChangeType == EMovieSceneDataChangeType::MovieSceneStructureItemRemoved ||
DataChangeType == EMovieSceneDataChangeType::MovieSceneStructureItemsChanged ||
DataChangeType == EMovieSceneDataChangeType::Unknown )
{
// When structure items are removed, or we don't know what may have changed, refresh the tree and instances immediately so that the data
// is in a consistent state when the UI is updated during the next tick.
EMovieScenePlayerStatus::Type StoredPlaybackState = GetPlaybackStatus();
SetPlaybackStatus( EMovieScenePlayerStatus::Stopped );
SelectionPreview.Empty();
RefreshTree();
SetPlaybackStatus( StoredPlaybackState );
}
else if (DataChangeType == EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately)
{
// Evaluate now
EvaluateInternal(PlayPosition.GetCurrentPositionAsRange());
}
else if (DataChangeType == EMovieSceneDataChangeType::RefreshAllImmediately)
{
RefreshTree();
// Evaluate now
EvaluateInternal(PlayPosition.GetCurrentPositionAsRange());
}
else
{
if (DataChangeType != EMovieSceneDataChangeType::TrackValueChanged)
{
bNeedTreeRefresh = true;
}
else if (NodeTree->UpdateFiltersOnTrackValueChanged())
{
bNeedTreeRefresh = true;
}
}
if (DataChangeType == EMovieSceneDataChangeType::TrackValueChanged ||
DataChangeType == EMovieSceneDataChangeType::TrackValueChangedRefreshImmediately ||
DataChangeType == EMovieSceneDataChangeType::Unknown ||
DataChangeType == EMovieSceneDataChangeType::MovieSceneStructureItemRemoved)
{
FSequencerEdMode* SequencerEdMode = (FSequencerEdMode*)(GLevelEditorModeTools().GetActiveMode(FSequencerEdMode::EM_SequencerMode));
if (SequencerEdMode != nullptr)
{
SequencerEdMode->CleanUpMeshTrails();
}
}
bGlobalMarkedFramesCached = false;
bNeedsEvaluate = true;
State.ClearObjectCaches(*this);
UpdatePlaybackRange();
OnMovieSceneDataChangedDelegate.Broadcast(DataChangeType);
}
static bool bRefreshTreeGuard = false;
void FSequencer::RefreshTree()
{
if (bRefreshTreeGuard == false)
{
TGuardValue<bool> Guard(bRefreshTreeGuard, true);
SequencerWidget->UpdateLayoutTree();
bNeedTreeRefresh = false;
OnTreeViewChangedDelegate.Broadcast();
// Force a broadcast of selection changed after the tree view has been updated, in the event that selection was suppressed while the tree was refreshing
Selection.Tick();
}
}
void FSequencer::RecreateCurveEditor()
{
using namespace UE::Sequencer;
FCurveEditorIntegrationExtension* CurveEditorIntegration = ViewModel->GetRootModel()->CastDynamic<FCurveEditorIntegrationExtension>();
if (ensure(CurveEditorIntegration))
{
CurveEditorIntegration->RecreateCurveEditor();
}
}
FAnimatedRange FSequencer::GetViewRange() const
{
FAnimatedRange AnimatedRange(FMath::Lerp(LastViewRange.GetLowerBoundValue(), TargetViewRange.GetLowerBoundValue(), ZoomCurve.GetLerp()),
FMath::Lerp(LastViewRange.GetUpperBoundValue(), TargetViewRange.GetUpperBoundValue(), ZoomCurve.GetLerp()));
if (ZoomAnimation.IsPlaying())
{
AnimatedRange.AnimationTarget = TargetViewRange;
}
return AnimatedRange;
}
FAnimatedRange FSequencer::GetClampRange() const
{
return GetFocusedMovieSceneSequence()->GetMovieScene()->GetEditorData().GetWorkingRange();
}
void FSequencer::SetClampRange(TRange<double> InNewClampRange)
{
FMovieSceneEditorData& EditorData = GetFocusedMovieSceneSequence()->GetMovieScene()->GetEditorData();
EditorData.WorkStart = InNewClampRange.GetLowerBoundValue();
EditorData.WorkEnd = InNewClampRange.GetUpperBoundValue();
}
TOptional<TRange<FFrameNumber>> FSequencer::GetSubSequenceRange() const
{
if (Settings->ShouldEvaluateSubSequencesInIsolation() || ActiveTemplateIDs.Num() == 1)
{
return TOptional<TRange<FFrameNumber>>();
}
return SubSequenceRange;
}
TRange<FFrameNumber> FSequencer::GetSelectionRange() const
{
return GetFocusedMovieSceneSequence()->GetMovieScene()->GetSelectionRange();
}
void FSequencer::SetSelectionRange(TRange<FFrameNumber> Range)
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("SetSelectionRange_Transaction", "Set Selection Range"));
FocusedMovieScene->Modify();
FocusedMovieScene->SetSelectionRange(Range);
}
void FSequencer::SetSelectionRangeEnd(FFrameTime EndFrame)
{
const FFrameNumber LocalTime = EndFrame.FrameNumber;
if (GetSelectionRange().GetLowerBoundValue() >= LocalTime)
{
SetSelectionRange(TRange<FFrameNumber>(LocalTime - 1, LocalTime));
}
else
{
SetSelectionRange(TRange<FFrameNumber>(GetSelectionRange().GetLowerBound(), LocalTime));
}
}
void FSequencer::SetSelectionRangeStart(FFrameTime StartFrame)
{
const FFrameNumber LocalTime = StartFrame.FrameNumber;
if (GetSelectionRange().GetUpperBoundValue() <= LocalTime)
{
SetSelectionRange(TRange<FFrameNumber>(LocalTime, LocalTime + 1));
}
else
{
SetSelectionRange(TRange<FFrameNumber>(LocalTime, GetSelectionRange().GetUpperBound()));
}
}
void FSequencer::SelectInSelectionRange(const TSharedPtr<UE::Sequencer::FViewModel>& Item, const TRange<FFrameNumber>& SelectionRange, bool bSelectKeys, bool bSelectSections)
{
using namespace UE::Sequencer;
IOutlinerExtension* Outliner = Item->CastThis<IOutlinerExtension>();
if (Outliner && Outliner->IsFilteredOut())
{
return;
}
if (bSelectKeys)
{
TArray<FKeyHandle> HandlesScratch;
for (TSharedPtr<FChannelModel> Channel : Item->GetDescendantsOfType<FChannelModel>())
{
TSharedPtr<IKeyArea> KeyArea = Channel->GetKeyArea();
UMovieSceneSection* Section = KeyArea->GetOwningSection();
if (Section)
{
HandlesScratch.Reset();
KeyArea->GetKeyHandles(HandlesScratch, SelectionRange);
for (int32 Index = 0; Index < HandlesScratch.Num(); ++Index)
{
Selection.AddToSelection(FSequencerSelectedKey(*Section, Channel, HandlesScratch[Index]));
}
}
}
}
if (bSelectSections)
{
for (TSharedPtr<FSectionModel> SectionModel : Item->GetDescendantsOfType<FSectionModel>())
{
TRange<FFrameNumber> SectionRange = SectionModel->GetRange();
if (SectionRange.Overlaps(SelectionRange) && SectionRange.GetLowerBound().IsClosed() && SectionRange.GetUpperBound().IsClosed())
{
Selection.AddToSelection(SectionModel);
}
}
}
for (TSharedPtr<FViewModel> Child : Item->GetChildren())
{
SelectInSelectionRange(Child, SelectionRange, bSelectKeys, bSelectSections);
}
}
void FSequencer::ClearSelectionRange()
{
SetSelectionRange(TRange<FFrameNumber>::Empty());
}
void FSequencer::SelectInSelectionRange(bool bSelectKeys, bool bSelectSections)
{
using namespace UE::Sequencer;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
TRange<FFrameNumber> SelectionRange = MovieScene->GetSelectionRange();
// Don't empty all selection, just keys and sections
Selection.SuspendBroadcast();
Selection.EmptySelectedKeys();
Selection.EmptySelectedTrackAreaItems();
for (const TViewModelPtr<IOutlinerExtension>& DisplayNode : NodeTree->GetRootNodes())
{
SelectInSelectionRange(DisplayNode, SelectionRange, bSelectKeys, bSelectSections);
}
Selection.ResumeBroadcast();
}
void FSequencer::SelectForward()
{
using namespace UE::Sequencer;
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameNumber CurrentFrame = GetLocalTime().ConvertTo(TickResolution).CeilToFrame();
TRange<FFrameNumber> SelectionRange(CurrentFrame, TNumericLimits<FFrameNumber>::Max());
TSet<TWeakPtr<FViewModel>> SelectedItems = Selection.GetNodesWithSelectedKeysOrSections();
if (SelectedItems.Num() == 0)
{
SelectedItems = Selection.GetSelectedOutlinerItems();
}
if (SelectedItems.Num() == 0)
{
TArray<TSharedPtr<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
for (TSharedPtr<FViewModel> Node : AllNodes)
{
SelectedItems.Add(Node);
}
}
if (SelectedItems.Num() > 0)
{
Selection.SuspendBroadcast();
Selection.EmptySelectedKeys();
Selection.EmptySelectedTrackAreaItems();
for (const TWeakPtr<FViewModel>& WeakItem : SelectedItems)
{
if (TSharedPtr<FViewModel> Item = WeakItem.Pin())
{
SelectInSelectionRange(Item, SelectionRange, true, true);
}
}
Selection.ResumeBroadcast();
}
}
void FSequencer::SelectBackward()
{
using namespace UE::Sequencer;
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameNumber CurrentFrame = GetLocalTime().ConvertTo(TickResolution).CeilToFrame();
TRange<FFrameNumber> SelectionRange(CurrentFrame, TNumericLimits<FFrameNumber>::Max());
TSet<TWeakPtr<FViewModel>> SelectedItems = Selection.GetNodesWithSelectedKeysOrSections();
if (SelectedItems.Num() == 0)
{
SelectedItems = Selection.GetSelectedOutlinerItems();
}
if (SelectedItems.Num() == 0)
{
TArray<TSharedPtr<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
for (const TSharedPtr<FViewModel>& Node : AllNodes)
{
SelectedItems.Add(Node);
}
}
if (SelectedItems.Num() > 0)
{
Selection.SuspendBroadcast();
Selection.EmptySelectedKeys();
Selection.EmptySelectedTrackAreaItems();
for (const TWeakPtr<FViewModel>& WeakItem : SelectedItems)
{
if (TSharedPtr<FViewModel> Item = WeakItem.Pin())
{
SelectInSelectionRange(Item, SelectionRange, true, true);
}
}
Selection.ResumeBroadcast();
}
}
TRange<FFrameNumber> FSequencer::GetPlaybackRange() const
{
return GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange();
}
void FSequencer::SetPlaybackRange(TRange<FFrameNumber> Range)
{
if (ensure(Range.HasLowerBound() && Range.HasUpperBound()))
{
if (!IsPlaybackRangeLocked())
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (FocusedMovieScene)
{
TRange<FFrameNumber> CurrentRange = FocusedMovieScene->GetPlaybackRange();
const FScopedTransaction Transaction(LOCTEXT("SetPlaybackRange_Transaction", "Set Playback Range"));
FocusedMovieScene->SetPlaybackRange(Range);
// If we're in a subsequence, compensate the start offset, so that it appears decoupled from the
// playback range (ie. the cut in frame remains the same)
if (ActiveTemplateIDs.Num() > 1)
{
if (UMovieSceneSubSection* SubSection = FindSubSection(ActiveTemplateIDs.Last()))
{
FFrameNumber LowerBoundDiff = Range.GetLowerBoundValue() - CurrentRange.GetLowerBoundValue();
FFrameNumber StartFrameOffset = SubSection->Parameters.StartFrameOffset - LowerBoundDiff;
SubSection->Modify();
SubSection->Parameters.StartFrameOffset = StartFrameOffset;
}
}
bNeedsEvaluate = true;
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
}
}
}
UMovieSceneSection* FSequencer::FindNextOrPreviousShot(UMovieSceneSequence* Sequence, FFrameNumber SearchFromTime, const bool bNextShot) const
{
UMovieScene* OwnerMovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = OwnerMovieScene->FindMasterTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack)
{
return nullptr;
}
FFrameNumber MinTime = TNumericLimits<FFrameNumber>::Max();
TMap<FFrameNumber, int32> StartTimeMap;
for (int32 SectionIndex = 0; SectionIndex < CinematicShotTrack->GetAllSections().Num(); ++SectionIndex)
{
UMovieSceneSection* ShotSection = CinematicShotTrack->GetAllSections()[SectionIndex];
if (ShotSection && ShotSection->HasStartFrame())
{
StartTimeMap.Add(ShotSection->GetInclusiveStartFrame(), SectionIndex);
}
}
StartTimeMap.KeySort(TLess<FFrameNumber>());
int32 MinShotIndex = -1;
for (auto StartTimeIt = StartTimeMap.CreateIterator(); StartTimeIt; ++StartTimeIt)
{
FFrameNumber StartTime = StartTimeIt->Key;
if (bNextShot)
{
if (StartTime > SearchFromTime)
{
FFrameNumber DiffTime = FMath::Abs(StartTime - SearchFromTime);
if (DiffTime < MinTime)
{
MinTime = DiffTime;
MinShotIndex = StartTimeIt->Value;
}
}
}
else
{
if (SearchFromTime >= StartTime)
{
FFrameNumber DiffTime = FMath::Abs(StartTime - SearchFromTime);
if (DiffTime < MinTime)
{
MinTime = DiffTime;
MinShotIndex = StartTimeIt->Value;
}
}
}
}
int32 TargetShotIndex = -1;
if (bNextShot)
{
TargetShotIndex = MinShotIndex;
}
else
{
int32 PreviousShotIndex = -1;
for (auto StartTimeIt = StartTimeMap.CreateIterator(); StartTimeIt; ++StartTimeIt)
{
if (StartTimeIt->Value == MinShotIndex)
{
if (PreviousShotIndex != -1)
{
TargetShotIndex = PreviousShotIndex;
}
break;
}
PreviousShotIndex = StartTimeIt->Value;
}
}
if (TargetShotIndex == -1)
{
return nullptr;
}
return CinematicShotTrack->GetAllSections()[TargetShotIndex];
}
void FSequencer::SetSelectionRangeToShot(const bool bNextShot)
{
UMovieSceneSection* TargetShotSection = FindNextOrPreviousShot(GetFocusedMovieSceneSequence(), GetLocalTime().Time.FloorToFrame(), bNextShot);
TRange<FFrameNumber> NewSelectionRange = TargetShotSection ? TargetShotSection->GetRange() : TRange<FFrameNumber>::All();
if (NewSelectionRange.GetLowerBound().IsClosed() && NewSelectionRange.GetUpperBound().IsClosed())
{
SetSelectionRange(NewSelectionRange);
}
}
void FSequencer::SetPlaybackRangeToAllShots()
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = OwnerMovieScene->FindMasterTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack || CinematicShotTrack->GetAllSections().Num() == 0)
{
return;
}
TRange<FFrameNumber> NewRange = CinematicShotTrack->GetAllSections()[0]->GetRange();
for (UMovieSceneSection* ShotSection : CinematicShotTrack->GetAllSections())
{
if (ShotSection && ShotSection->HasStartFrame() && ShotSection->HasEndFrame())
{
NewRange = TRange<FFrameNumber>::Hull(ShotSection->GetRange(), NewRange);
}
}
SetPlaybackRange(NewRange);
}
bool FSequencer::IsPlaybackRangeLocked() const
{
if (IsReadOnly())
{
return true;
}
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
if (FocusedMovieSceneSequence != nullptr)
{
UMovieScene* MovieScene = FocusedMovieSceneSequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
return true;
}
return MovieScene->IsPlaybackRangeLocked();
}
return false;
}
void FSequencer::TogglePlaybackRangeLocked()
{
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
if ( FocusedMovieSceneSequence != nullptr )
{
UMovieScene* MovieScene = FocusedMovieSceneSequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction TogglePlaybackRangeLockTransaction( NSLOCTEXT( "Sequencer", "TogglePlaybackRangeLocked", "Toggle playback range lock" ) );
MovieScene->Modify();
MovieScene->SetPlaybackRangeLocked( !MovieScene->IsPlaybackRangeLocked() );
}
}
void FSequencer::ResetViewRange()
{
TRange<double> PlayRangeSeconds = GetPlaybackRange() / GetFocusedTickResolution();
const double OutputViewSize = PlayRangeSeconds.Size<double>();
const double OutputChange = OutputViewSize * 0.1f;
if (OutputChange > 0)
{
PlayRangeSeconds = UE::MovieScene::ExpandRange(PlayRangeSeconds, OutputChange);
SetClampRange(PlayRangeSeconds);
SetViewRange(PlayRangeSeconds, EViewRangeInterpolation::Animated);
}
}
void FSequencer::ZoomViewRange(float InZoomDelta)
{
float LocalViewRangeMax = TargetViewRange.GetUpperBoundValue();
float LocalViewRangeMin = TargetViewRange.GetLowerBoundValue();
const double CurrentTime = GetLocalTime().AsSeconds();
const double OutputViewSize = LocalViewRangeMax - LocalViewRangeMin;
const double OutputChange = OutputViewSize * InZoomDelta;
float CurrentPositionFraction = (CurrentTime - LocalViewRangeMin) / OutputViewSize;
double NewViewOutputMin = LocalViewRangeMin - (OutputChange * CurrentPositionFraction);
double NewViewOutputMax = LocalViewRangeMax + (OutputChange * (1.f - CurrentPositionFraction));
if (NewViewOutputMin < NewViewOutputMax)
{
SetViewRange(TRange<double>(NewViewOutputMin, NewViewOutputMax), EViewRangeInterpolation::Animated);
}
}
void FSequencer::ZoomInViewRange()
{
ZoomViewRange(-0.1f);
}
void FSequencer::ZoomOutViewRange()
{
ZoomViewRange(0.1f);
}
void FSequencer::UpdatePlaybackRange()
{
if (!Settings->ShouldKeepPlayRangeInSectionBounds())
{
return;
}
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
TArray<UMovieSceneSection*> AllSections = FocusedMovieScene->GetAllSections();
if (AllSections.Num() > 0 && !IsPlaybackRangeLocked())
{
TRange<FFrameNumber> NewBounds = TRange<FFrameNumber>::Empty();
for (UMovieSceneSection* Section : AllSections)
{
NewBounds = TRange<FFrameNumber>::Hull(Section->ComputeEffectiveRange(), NewBounds);
}
// When the playback range is determined by the section bounds, don't mark the change in the playback range otherwise the scene will be marked dirty
if (!NewBounds.IsDegenerate())
{
const bool bAlwaysMarkDirty = false;
FocusedMovieScene->SetPlaybackRange(NewBounds, bAlwaysMarkDirty);
}
}
}
EAutoChangeMode FSequencer::GetAutoChangeMode() const
{
return Settings->GetAutoChangeMode();
}
void FSequencer::SetAutoChangeMode(EAutoChangeMode AutoChangeMode)
{
Settings->SetAutoChangeMode(AutoChangeMode);
}
EAllowEditsMode FSequencer::GetAllowEditsMode() const
{
return Settings->GetAllowEditsMode();
}
void FSequencer::SetAllowEditsMode(EAllowEditsMode AllowEditsMode)
{
Settings->SetAllowEditsMode(AllowEditsMode);
}
EKeyGroupMode FSequencer::GetKeyGroupMode() const
{
return Settings->GetKeyGroupMode();
}
void FSequencer::SetKeyGroupMode(EKeyGroupMode Mode)
{
Settings->SetKeyGroupMode(Mode);
}
EMovieSceneKeyInterpolation FSequencer::GetKeyInterpolation() const
{
return Settings->GetKeyInterpolation();
}
void FSequencer::SetKeyInterpolation(EMovieSceneKeyInterpolation InKeyInterpolation)
{
Settings->SetKeyInterpolation(InKeyInterpolation);
}
bool FSequencer::GetInfiniteKeyAreas() const
{
return Settings->GetInfiniteKeyAreas();
}
void FSequencer::SetInfiniteKeyAreas(bool bInfiniteKeyAreas)
{
Settings->SetInfiniteKeyAreas(bInfiniteKeyAreas);
}
bool FSequencer::GetAutoSetTrackDefaults() const
{
return Settings->GetAutoSetTrackDefaults();
}
FQualifiedFrameTime FSequencer::GetLocalTime() const
{
const FFrameRate FocusedResolution = GetFocusedTickResolution();
const FFrameTime CurrentPosition = PlayPosition.GetCurrentPosition();
const FFrameTime RootTime = ConvertFrameTime(CurrentPosition, PlayPosition.GetInputRate(), PlayPosition.GetOutputRate());
return FQualifiedFrameTime(RootTime * RootToLocalTransform, FocusedResolution);
}
uint32 FSequencer::GetLocalLoopIndex() const
{
if (RootToLocalLoopCounter.WarpCounts.Num() == 0)
{
return FMovieSceneTimeWarping::InvalidWarpCount;
}
else
{
const bool bIsScrubbing = GetPlaybackStatus() == EMovieScenePlayerStatus::Scrubbing;
return RootToLocalLoopCounter.WarpCounts.Last() + (bIsScrubbing ? LocalLoopIndexOffsetDuringScrubbing : 0);
}
}
FQualifiedFrameTime FSequencer::GetGlobalTime() const
{
FFrameTime RootTime = ConvertFrameTime(PlayPosition.GetCurrentPosition(), PlayPosition.GetInputRate(), PlayPosition.GetOutputRate());
return FQualifiedFrameTime(RootTime, PlayPosition.GetOutputRate());
}
void FSequencer::SetLocalTime( FFrameTime NewTime, ESnapTimeMode SnapTimeMode, bool bEvaluate)
{
FFrameRate LocalResolution = GetFocusedTickResolution();
// Ensure the time is in the current view
if (IsAutoScrollEnabled() || GetPlaybackStatus() != EMovieScenePlayerStatus::Playing)
{
ScrollIntoView(NewTime / LocalResolution);
}
// Perform snapping
if ((SnapTimeMode & ESnapTimeMode::STM_Interval) && Settings->GetIsSnapEnabled())
{
FFrameRate LocalDisplayRate = GetFocusedDisplayRate();
NewTime = FFrameRate::TransformTime(FFrameRate::TransformTime(NewTime, LocalResolution, LocalDisplayRate).RoundToFrame(), LocalDisplayRate, LocalResolution);
}
if (SnapTimeMode & ESnapTimeMode::STM_Keys)
{
ENearestKeyOption NearestKeyOption = ENearestKeyOption::NKO_None;
if (Settings->GetSnapPlayTimeToKeys() || FSlateApplication::Get().GetModifierKeys().IsShiftDown())
{
EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchKeys);
}
if (Settings->GetSnapPlayTimeToSections() || FSlateApplication::Get().GetModifierKeys().IsShiftDown())
{
EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchSections);
}
if (Settings->GetSnapPlayTimeToMarkers() || FSlateApplication::Get().GetModifierKeys().IsShiftDown())
{
EnumAddFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchMarkers);
}
NewTime = OnGetNearestKey(NewTime, NearestKeyOption);
}
SetLocalTimeDirectly(NewTime, bEvaluate);
}
void FSequencer::SetLocalTimeDirectly(FFrameTime NewTime, bool bEvaluate)
{
TWeakPtr<SWidget> PreviousFocusedWidget = FSlateApplication::Get().GetKeyboardFocusedWidget();
// Clear focus before setting time in case there's a key editor value selected that gets committed to a newly selected key on UserMovedFocus
if (GetPlaybackStatus() == EMovieScenePlayerStatus::Stopped)
{
FSlateApplication::Get().ClearKeyboardFocus(EFocusCause::Cleared);
}
// Transform the time to the root time-space
SetGlobalTime(NewTime * RootToLocalTransform.InverseFromWarp(RootToLocalLoopCounter), bEvaluate);
if (PreviousFocusedWidget.IsValid())
{
FSlateApplication::Get().SetKeyboardFocus(PreviousFocusedWidget.Pin());
}
}
void FSequencer::SetGlobalTime(FFrameTime NewTime, bool bEvaluate)
{
NewTime = ConvertFrameTime(NewTime, GetRootTickResolution(), PlayPosition.GetInputRate());
if (PlayPosition.GetEvaluationType() == EMovieSceneEvaluationType::FrameLocked)
{
NewTime = NewTime.FloorToFrame();
}
// Don't update the sequence if the time hasn't changed as this will cause duplicate events and the like to fire.
// If we need to reevaluate the sequence at the same time for whetever reason, we should call ForceEvaluate()
TOptional<FFrameTime> CurrentPosition = PlayPosition.GetCurrentPosition();
if (PlayPosition.GetCurrentPosition() != NewTime)
{
FMovieSceneEvaluationRange EvalRange = PlayPosition.JumpTo(NewTime);
if (bEvaluate)
{
EvaluateInternal(EvalRange);
}
}
if (AutoScrubTarget.IsSet())
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
AutoScrubTarget.Reset();
}
}
void FSequencer::PlayTo(FMovieSceneSequencePlaybackParams PlaybackParams)
{
FFrameTime PlayToTime = GetLocalTime().Time;
if (PlaybackParams.PositionType == EMovieScenePositionType::Frame)
{
PlayToTime = (PlaybackParams.Frame / GetFocusedDisplayRate()) * GetFocusedTickResolution();
}
else if (PlaybackParams.PositionType == EMovieScenePositionType::Time)
{
PlayToTime = PlaybackParams.Time * GetFocusedTickResolution();
}
else if (PlaybackParams.PositionType == EMovieScenePositionType::MarkedFrame)
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (FocusedMovieSequence != nullptr)
{
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (FocusedMovieScene != nullptr)
{
int32 MarkedIndex = FocusedMovieScene->FindMarkedFrameByLabel(PlaybackParams.MarkedFrame);
if (MarkedIndex != INDEX_NONE)
{
PlayToTime = FocusedMovieScene->GetMarkedFrames()[MarkedIndex].FrameNumber;
}
}
}
}
if (GetLocalTime().Time < PlayToTime)
{
PlaybackSpeed = FMath::Abs(PlaybackSpeed);
}
else
{
PlaybackSpeed = -FMath::Abs(PlaybackSpeed);
}
OnPlay(false);
PauseOnFrame = PlayToTime;
}
void FSequencer::ForceEvaluate()
{
EvaluateInternal(PlayPosition.GetCurrentPositionAsRange());
}
void FSequencer::EvaluateInternal(FMovieSceneEvaluationRange InRange, bool bHasJumped)
{
if (Settings->ShouldCompileDirectorOnEvaluate())
{
RecompileDirtyDirectors();
}
bNeedsEvaluate = false;
UpdateCachedPlaybackContextAndClient();
if (EventContextsAttribute.IsBound())
{
CachedEventContexts.Reset();
for (UObject* Object : EventContextsAttribute.Get())
{
CachedEventContexts.Add(Object);
}
}
FMovieSceneContext Context = FMovieSceneContext(InRange, PlaybackState).SetIsSilent(SilentModeCount != 0);
Context.SetHasJumped(bHasJumped);
FMovieSceneSequenceID RootOverride = MovieSceneSequenceID::Root;
if (Settings->ShouldEvaluateSubSequencesInIsolation())
{
RootOverride = ActiveTemplateIDs.Top();
}
RootTemplateInstance.Evaluate(Context, *this);
SuppressAutoEvalSignature.Reset();
if (RootTemplateInstance.GetEntitySystemRunner().IsAttachedToLinker())
{
RootTemplateInstance.GetEntitySystemRunner().Flush();
}
if (Settings->ShouldRerunConstructionScripts())
{
RerunConstructionScripts();
}
if (!IsInSilentMode())
{
OnGlobalTimeChangedDelegate.Broadcast();
GetRendererModule().InvalidatePathTracedOutput();
}
}
void FSequencer::UpdateCachedPlaybackContextAndClient()
{
TWeakObjectPtr<UObject> NewPlaybackContext;
TWeakInterfacePtr<IMovieScenePlaybackClient> NewPlaybackClient;
if (PlaybackContextAttribute.IsBound())
{
NewPlaybackContext = PlaybackContextAttribute.Get();
}
if (PlaybackClientAttribute.IsBound())
{
NewPlaybackClient = TWeakInterfacePtr<IMovieScenePlaybackClient>(PlaybackClientAttribute.Get());
}
if (CachedPlaybackContext != NewPlaybackContext || CachedPlaybackClient != NewPlaybackClient)
{
PrePossessionViewTargets.Reset();
State.ClearObjectCaches(*this);
RestorePreAnimatedState();
CachedPlaybackContext = NewPlaybackContext;
CachedPlaybackClient = NewPlaybackClient;
RootTemplateInstance.PlaybackContextChanged(*this);
}
}
void FSequencer::UpdateCachedCameraActors()
{
const uint32 CurrentStateSerial = State.GetSerialNumber();
if (CurrentStateSerial == LastKnownStateSerial)
{
return;
}
LastKnownStateSerial = CurrentStateSerial;
CachedCameraActors.Reset();
TArray<FMovieSceneSequenceID> SequenceIDs;
SequenceIDs.Add(MovieSceneSequenceID::Root);
if (const FMovieSceneSequenceHierarchy* Hierarchy = RootTemplateInstance.GetHierarchy())
{
Hierarchy->AllSubSequenceIDs(SequenceIDs);
}
for (FMovieSceneSequenceID SequenceID : SequenceIDs)
{
if (UMovieSceneSequence* Sequence = RootTemplateInstance.GetSequence(SequenceID))
{
if (UMovieScene* MovieScene = Sequence->GetMovieScene())
{
TArray<FGuid> BindingGuids;
for (uint32 SpawnableIndex = 0, SpawnableCount = MovieScene->GetSpawnableCount();
SpawnableIndex < SpawnableCount;
++SpawnableIndex)
{
const FMovieSceneSpawnable& Spawnable = MovieScene->GetSpawnable(SpawnableIndex);
BindingGuids.Add(Spawnable.GetGuid());
}
for (uint32 PossessableIndex = 0, PossessableCount = MovieScene->GetPossessableCount();
PossessableIndex < PossessableCount;
++PossessableIndex)
{
const FMovieScenePossessable& Possessable = MovieScene->GetPossessable(PossessableIndex);
BindingGuids.Add(Possessable.GetGuid());
}
const FMovieSceneObjectCache& ObjectCache = State.GetObjectCache(SequenceID);
for (const FGuid& BindingGuid : BindingGuids)
{
for (TWeakObjectPtr<> BoundObject : ObjectCache.IterateBoundObjects(BindingGuid))
{
if (AActor* BoundActor = Cast<AActor>(BoundObject.Get()))
{
UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromActor(BoundActor);
if (CameraComponent)
{
CachedCameraActors.Add(BoundActor, BindingGuid);
}
}
}
}
}
}
}
}
void FSequencer::ScrollIntoView(float InLocalTime)
{
float RangeOffset = CalculateAutoscrollEncroachment(InLocalTime).Get(0.f);
// When not scrubbing, we auto scroll the view range immediately
if (RangeOffset != 0.f)
{
TRange<double> WorkingRange = GetClampRange();
// Adjust the offset so that the target range will be within the working range.
if (TargetViewRange.GetLowerBoundValue() + RangeOffset < WorkingRange.GetLowerBoundValue())
{
RangeOffset = WorkingRange.GetLowerBoundValue() - TargetViewRange.GetLowerBoundValue();
}
else if (TargetViewRange.GetUpperBoundValue() + RangeOffset > WorkingRange.GetUpperBoundValue())
{
RangeOffset = WorkingRange.GetUpperBoundValue() - TargetViewRange.GetUpperBoundValue();
}
SetViewRange(TRange<double>(TargetViewRange.GetLowerBoundValue() + RangeOffset, TargetViewRange.GetUpperBoundValue() + RangeOffset), EViewRangeInterpolation::Immediate);
}
}
void FSequencer::UpdateAutoScroll(double NewTime, float ThresholdPercentage)
{
AutoscrollOffset = CalculateAutoscrollEncroachment(NewTime, ThresholdPercentage);
if (!AutoscrollOffset.IsSet())
{
AutoscrubOffset.Reset();
return;
}
TRange<double> ViewRange = GetViewRange();
const double Threshold = (ViewRange.GetUpperBoundValue() - ViewRange.GetLowerBoundValue()) * ThresholdPercentage;
const FQualifiedFrameTime LocalTime = GetLocalTime();
// If we have no autoscrub offset yet, we move the scrub position to the boundary of the autoscroll threasdhold, then autoscrub from there
if (!AutoscrubOffset.IsSet())
{
if (AutoscrollOffset.GetValue() < 0 && LocalTime.AsSeconds() > ViewRange.GetLowerBoundValue() + Threshold)
{
SetLocalTimeLooped( (ViewRange.GetLowerBoundValue() + Threshold) * LocalTime.Rate );
}
else if (AutoscrollOffset.GetValue() > 0 && LocalTime.AsSeconds() < ViewRange.GetUpperBoundValue() - Threshold)
{
SetLocalTimeLooped( (ViewRange.GetUpperBoundValue() - Threshold) * LocalTime.Rate );
}
}
// Don't autoscrub if we're at the extremes of the movie scene range
const FMovieSceneEditorData& EditorData = GetFocusedMovieSceneSequence()->GetMovieScene()->GetEditorData();
if (NewTime < EditorData.WorkStart + Threshold ||
NewTime > EditorData.WorkEnd - Threshold
)
{
AutoscrubOffset.Reset();
return;
}
// Scrub at the same rate we scroll
AutoscrubOffset = AutoscrollOffset;
}
TOptional<float> FSequencer::CalculateAutoscrollEncroachment(double NewTime, float ThresholdPercentage) const
{
enum class EDirection { Positive, Negative };
const EDirection Movement = NewTime - GetLocalTime().AsSeconds() >= 0 ? EDirection::Positive : EDirection::Negative;
const TRange<double> CurrentRange = GetViewRange();
const double RangeMin = CurrentRange.GetLowerBoundValue(), RangeMax = CurrentRange.GetUpperBoundValue();
const double AutoScrollThreshold = (RangeMax - RangeMin) * ThresholdPercentage;
if (Movement == EDirection::Negative && NewTime < RangeMin + AutoScrollThreshold)
{
// Scrolling backwards in time, and have hit the threshold
return NewTime - (RangeMin + AutoScrollThreshold);
}
if (Movement == EDirection::Positive && NewTime > RangeMax - AutoScrollThreshold)
{
// Scrolling forwards in time, and have hit the threshold
return NewTime - (RangeMax - AutoScrollThreshold);
}
return TOptional<float>();
}
void FSequencer::AutoScrubToTime(FFrameTime DestinationTime)
{
AutoScrubTarget = FAutoScrubTarget(DestinationTime, GetLocalTime().Time, FPlatformTime::Seconds());
}
void FSequencer::SetPerspectiveViewportPossessionEnabled(bool bEnabled)
{
bPerspectiveViewportPossessionEnabled = bEnabled;
}
void FSequencer::SetPerspectiveViewportCameraCutEnabled(bool bEnabled)
{
if (bPerspectiveViewportCameraCutEnabled == bEnabled)
{
return;
}
bPerspectiveViewportCameraCutEnabled = bEnabled;
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC != nullptr && LevelVC->AllowsCinematicControl() && LevelVC->GetViewMode() != VMI_Unknown)
{
if (bEnabled)
{
LevelVC->ViewModifiers.AddRaw(this, &FSequencer::ModifyViewportClientView);
}
else
{
LevelVC->ViewModifiers.RemoveAll(this);
}
}
}
}
void FSequencer::ModifyViewportClientView(FEditorViewportViewModifierParams& Params)
{
if (!ViewModifierInfo.bApplyViewModifier)
{
return;
}
const float BlendFactor = ViewModifierInfo.BlendFactor;
AActor* CameraActor = ViewModifierInfo.NextCamera.Get();
AActor* PreviousCameraActor = ViewModifierInfo.PreviousCamera.Get();
UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(CameraActor);
UCameraComponent* PreviousCameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(PreviousCameraActor);
if (CameraActor)
{
const FVector ViewLocation = CameraComponent ? CameraComponent->GetComponentLocation() : CameraActor->GetActorLocation();
const FRotator ViewRotation = CameraComponent ? CameraComponent->GetComponentRotation() : CameraActor->GetActorRotation();
// If we have no previous camera actor or component, it means we're blending from the original
// editor viewport camera transform that we cached.
const FVector PreviousViewLocation = PreviousCameraComponent ?
PreviousCameraComponent->GetComponentLocation() :
(PreviousCameraActor ? PreviousCameraActor->GetActorLocation() : PreAnimatedViewportLocation);
const FRotator PreviousViewRotation = PreviousCameraComponent ?
PreviousCameraComponent->GetComponentRotation() :
(PreviousCameraActor ? PreviousCameraActor->GetActorRotation() : PreAnimatedViewportRotation);
const FVector BlendedLocation = FMath::Lerp(PreviousViewLocation, ViewLocation, BlendFactor);
const FRotator BlendedRotation = FMath::Lerp(PreviousViewRotation, ViewRotation, BlendFactor);
Params.ViewInfo.Location = BlendedLocation;
Params.ViewInfo.Rotation = BlendedRotation;
}
else
{
// Blending from a shot back to editor camera.
const FVector PreviousViewLocation = PreviousCameraComponent ?
PreviousCameraComponent->GetComponentLocation() :
(PreviousCameraActor ? PreviousCameraActor->GetActorLocation() : PreAnimatedViewportLocation);
const FRotator PreviousViewRotation = PreviousCameraComponent ?
PreviousCameraComponent->GetComponentRotation() :
(PreviousCameraActor ? PreviousCameraActor->GetActorRotation() : PreAnimatedViewportRotation);
const FVector BlendedLocation = FMath::Lerp(PreviousViewLocation, PreAnimatedViewportLocation, BlendFactor);
const FRotator BlendedRotation = FMath::Lerp(PreviousViewRotation, PreAnimatedViewportRotation, BlendFactor);
Params.ViewInfo.Location = BlendedLocation;
Params.ViewInfo.Rotation = BlendedRotation;
}
// Deal with camera properties.
if (CameraComponent)
{
const float PreviousFOV = PreviousCameraComponent != nullptr ?
PreviousCameraComponent->FieldOfView : PreAnimatedViewportFOV;
const float BlendedFOV = FMath::Lerp(PreviousFOV, CameraComponent->FieldOfView, BlendFactor);
Params.ViewInfo.FOV = BlendedFOV;
}
else
{
const float PreviousFOV = PreviousCameraComponent != nullptr ?
PreviousCameraComponent->FieldOfView : PreAnimatedViewportFOV;
const float BlendedFOV = FMath::Lerp(PreviousFOV, PreAnimatedViewportFOV, BlendFactor);
Params.ViewInfo.FOV = BlendedFOV;
}
}
FString FSequencer::GetMovieRendererName() const
{
// If blank, default to the first available since we don't want the be using the Legacy one anyway, unless the user explicitly chooses it.
FString MovieRendererName = Settings->GetMovieRendererName();
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>("Sequencer");
if (MovieRendererName.IsEmpty() && SequencerModule.GetMovieRendererNames().Num() > 0)
{
MovieRendererName = SequencerModule.GetMovieRendererNames()[0];
Settings->SetMovieRendererName(MovieRendererName);
}
return MovieRendererName;
}
void FSequencer::RenderMovie(const TArray<UMovieSceneCinematicShotSection*>& InSections) const
{
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>("Sequencer");
if (IMovieRendererInterface* MovieRenderer = SequencerModule.GetMovieRenderer(GetMovieRendererName()))
{
MovieRenderer->RenderMovie(GetRootMovieSceneSequence(), InSections);
return;
}
if (InSections.Num() != 0)
{
RenderMovieInternal(InSections[0]->GetRange(), true);
}
}
void FSequencer::RenderMovieInternal(TRange<FFrameNumber> Range, bool bSetFrameOverrides) const
{
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>("Sequencer");
if (IMovieRendererInterface* MovieRenderer = SequencerModule.GetMovieRenderer(GetMovieRendererName()))
{
MovieRenderer->RenderMovie(GetRootMovieSceneSequence(), TArray<UMovieSceneCinematicShotSection*>());
return;
}
if (Range.GetLowerBound().IsOpen() || Range.GetUpperBound().IsOpen())
{
Range = TRange<FFrameNumber>::Hull(Range, GetPlaybackRange());
}
// If focused on a subsequence, transform the playback range to the root in order to always render from the root
if (GetRootMovieSceneSequence() != GetFocusedMovieSceneSequence())
{
bSetFrameOverrides = true;
if (const FMovieSceneSubSequenceData* SubSequenceData = RootTemplateInstance.FindSubData(GetFocusedTemplateID()))
{
Range = Range * SubSequenceData->RootToSequenceTransform.InverseLinearOnly();
}
}
FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
// Create a new movie scene capture object for an automated level sequence, and open the tab
UAutomatedLevelSequenceCapture* MovieSceneCapture = NewObject<UAutomatedLevelSequenceCapture>(GetTransientPackage(), UAutomatedLevelSequenceCapture::StaticClass(), UMovieSceneCapture::MovieSceneCaptureUIName, RF_Transient);
MovieSceneCapture->LoadFromConfig();
// Always render from the root
MovieSceneCapture->LevelSequenceAsset = GetRootMovieSceneSequence()->GetMovieScene()->GetOuter()->GetPathName();
FFrameRate DisplayRate = GetFocusedDisplayRate();
FFrameRate TickResolution = GetFocusedTickResolution();
MovieSceneCapture->Settings.FrameRate = DisplayRate;
MovieSceneCapture->Settings.ZeroPadFrameNumbers = Settings->GetZeroPadFrames();
MovieSceneCapture->Settings.bUseRelativeFrameNumbers = false;
FFrameNumber StartFrame = UE::MovieScene::DiscreteInclusiveLower(Range);
FFrameNumber EndFrame = UE::MovieScene::DiscreteExclusiveUpper(Range);
FFrameNumber RoundedStartFrame = FFrameRate::TransformTime(StartFrame, TickResolution, DisplayRate).CeilToFrame();
FFrameNumber RoundedEndFrame = FFrameRate::TransformTime(EndFrame, TickResolution, DisplayRate).CeilToFrame();
if (bSetFrameOverrides)
{
MovieSceneCapture->SetFrameOverrides(RoundedStartFrame, RoundedEndFrame);
}
else
{
if (!MovieSceneCapture->bUseCustomStartFrame)
{
MovieSceneCapture->CustomStartFrame = RoundedStartFrame;
}
if (!MovieSceneCapture->bUseCustomEndFrame)
{
MovieSceneCapture->CustomEndFrame = RoundedEndFrame;
}
}
// We create a new Numeric Type Interface that ties it's Capture/Resolution rates to the Capture Object so that it converts UI entries
// to the correct resolution for the capture, and not for the original sequence.
USequencerSettings* LocalSettings = Settings;
TAttribute<EFrameNumberDisplayFormats> GetDisplayFormatAttr = MakeAttributeLambda(
[LocalSettings]
{
if (LocalSettings)
{
return LocalSettings->GetTimeDisplayFormat();
}
return EFrameNumberDisplayFormats::Frames;
}
);
TAttribute<uint8> GetZeroPadFramesAttr = MakeAttributeLambda(
[LocalSettings]()->uint8
{
if (LocalSettings)
{
return LocalSettings->GetZeroPadFrames();
}
return 0;
}
);
// By using a TickResolution/DisplayRate that match the numbers entered via the numeric interface don't change frames of reference.
// This is used here because the movie scene capture works entirely on play rate resolution and has no knowledge of the internal resolution
// so we don't need to convert the user's input into internal resolution.
TAttribute<FFrameRate> GetFrameRateAttr = MakeAttributeLambda(
[MovieSceneCapture]
{
if (MovieSceneCapture)
{
return MovieSceneCapture->GetSettings().FrameRate;
}
return FFrameRate(30, 1);
}
);
// Create our numeric type interface so we can pass it to the time slider below.
TSharedPtr<INumericTypeInterface<double>> MovieSceneCaptureNumericInterface = MakeShareable(new FFrameNumberInterface(GetDisplayFormatAttr, GetZeroPadFramesAttr, GetFrameRateAttr, GetFrameRateAttr));
IMovieSceneCaptureDialogModule::Get().OpenDialog(LevelEditorModule.GetLevelEditorTabManager().ToSharedRef(), MovieSceneCapture, MovieSceneCaptureNumericInterface);
}
void FSequencer::EnterSilentMode()
{
if (SilentModeCount == 0)
{
CachedViewModifierInfo = ViewModifierInfo;
}
++SilentModeCount;
}
void FSequencer::ExitSilentMode()
{
--SilentModeCount;
ensure(SilentModeCount >= 0);
if (SilentModeCount == 0)
{
ViewModifierInfo = CachedViewModifierInfo;
}
}
ISequencer::FOnActorAddedToSequencer& FSequencer::OnActorAddedToSequencer()
{
return OnActorAddedToSequencerEvent;
}
ISequencer::FOnPreSave& FSequencer::OnPreSave()
{
return OnPreSaveEvent;
}
ISequencer::FOnPostSave& FSequencer::OnPostSave()
{
return OnPostSaveEvent;
}
ISequencer::FOnActivateSequence& FSequencer::OnActivateSequence()
{
return OnActivateSequenceEvent;
}
ISequencer::FOnCameraCut& FSequencer::OnCameraCut()
{
return OnCameraCutEvent;
}
TSharedRef<INumericTypeInterface<double>> FSequencer::GetNumericTypeInterface() const
{
return SequencerWidget->GetNumericTypeInterface();
}
TSharedRef<SWidget> FSequencer::MakeTimeRange(const TSharedRef<SWidget>& InnerContent, bool bShowWorkingRange, bool bShowViewRange, bool bShowPlaybackRange)
{
return SequencerWidget->MakeTimeRange(InnerContent, bShowWorkingRange, bShowViewRange, bShowPlaybackRange);
}
/** Attempt to find an object binding ID that relates to an unspawned spawnable object */
FGuid FindUnspawnedObjectGuid(UObject& InObject, UMovieSceneSequence& Sequence)
{
UMovieScene* MovieScene = Sequence.GetMovieScene();
// If the object is an archetype, the it relates to an unspawned spawnable.
UObject* ParentObject = Sequence.GetParentObject(&InObject);
if (ParentObject && FMovieSceneSpawnable::IsSpawnableTemplate(*ParentObject))
{
FMovieSceneSpawnable* ParentSpawnable = MovieScene->FindSpawnable([&](FMovieSceneSpawnable& InSpawnable){
return InSpawnable.GetObjectTemplate() == ParentObject;
});
if (ParentSpawnable)
{
UObject* ParentContext = ParentSpawnable->GetObjectTemplate();
// The only way to find the object now is to resolve all the child bindings, and see if they are the same
for (const FGuid& ChildGuid : ParentSpawnable->GetChildPossessables())
{
const bool bHasObject = Sequence.LocateBoundObjects(ChildGuid, ParentContext).Contains(&InObject);
if (bHasObject)
{
return ChildGuid;
}
}
}
}
else if (FMovieSceneSpawnable::IsSpawnableTemplate(InObject))
{
FMovieSceneSpawnable* SpawnableByArchetype = MovieScene->FindSpawnable([&](FMovieSceneSpawnable& InSpawnable){
return InSpawnable.GetObjectTemplate() == &InObject;
});
if (SpawnableByArchetype)
{
return SpawnableByArchetype->GetGuid();
}
}
return FGuid();
}
UMovieSceneFolder* FSequencer::CreateFoldersRecursively(const TArray<FName>& FolderPath, int32 FolderPathIndex, UMovieScene* OwningMovieScene, UMovieSceneFolder* ParentFolder, TArrayView<UMovieSceneFolder* const> FoldersToSearch)
{
// An empty folder path won't create a folder
if (FolderPath.Num() == 0)
{
return ParentFolder;
}
check(FolderPathIndex < FolderPath.Num());
// Look to see if there's already a folder with the right name
UMovieSceneFolder* FolderToUse = nullptr;
FName DesiredFolderName = FolderPath[FolderPathIndex];
for (UMovieSceneFolder* Folder : FoldersToSearch)
{
if (Folder->GetFolderName() == DesiredFolderName)
{
FolderToUse = Folder;
break;
}
}
// If we didn't find a folder with the desired name then we create a new folder as a sibling of the existing folders.
if (FolderToUse == nullptr)
{
FolderToUse = NewObject<UMovieSceneFolder>(OwningMovieScene, NAME_None, RF_Transactional);
FolderToUse->SetFolderName(DesiredFolderName);
if (ParentFolder)
{
// Add the new folder as a sibling of the folders we were searching in.
ParentFolder->AddChildFolder(FolderToUse);
}
else
{
// If we have no parent folder then we must be at the root so we add it to the root of the movie scene
OwningMovieScene->Modify();
OwningMovieScene->AddRootFolder(FolderToUse);
}
}
// Increment which part of the path we're searching in and then recurse inside of the folder we found (or created).
FolderPathIndex++;
if (FolderPathIndex < FolderPath.Num())
{
return CreateFoldersRecursively(FolderPath, FolderPathIndex, OwningMovieScene, FolderToUse, FolderToUse->GetChildFolders());
}
// We return the tail folder created so that the user can add things to it.
return FolderToUse;
}
FGuid FSequencer::GetHandleToObject( UObject* Object, bool bCreateHandleIfMissing, const FName& CreatedFolderName )
{
if (Object == nullptr)
{
return FGuid();
}
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
UMovieScene* FocusedMovieScene = FocusedMovieSceneSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return FGuid();
}
if (FocusedMovieScene->IsReadOnly())
{
return FGuid();
}
// Attempt to resolve the object through the movie scene instance first,
FGuid ObjectGuid = FindObjectId(*Object, ActiveTemplateIDs.Top());
if (ObjectGuid.IsValid())
{
// Check here for spawnable otherwise spawnables get recreated as possessables, which doesn't make sense
FMovieSceneSpawnable* Spawnable = FocusedMovieScene->FindSpawnable(ObjectGuid);
if (Spawnable)
{
return ObjectGuid;
}
// Make sure that the possessable is still valid, if it's not remove the binding so new one
// can be created. This can happen due to undo.
FMovieScenePossessable* Possessable = FocusedMovieScene->FindPossessable(ObjectGuid);
if(Possessable == nullptr)
{
FocusedMovieSceneSequence->UnbindPossessableObjects(ObjectGuid);
ObjectGuid.Invalidate();
}
}
else
{
ObjectGuid = FindUnspawnedObjectGuid(*Object, *FocusedMovieSceneSequence);
}
if (ObjectGuid.IsValid() || IsReadOnly())
{
return ObjectGuid;
}
UObject* PlaybackContext = PlaybackContextAttribute.Get(nullptr);
// If the object guid was not found attempt to add it
// Note: Only possessed actors can be added like this
if (FocusedMovieSceneSequence->CanPossessObject(*Object, PlaybackContext) && bCreateHandleIfMissing)
{
AActor* PossessedActor = Cast<AActor>(Object);
ObjectGuid = CreateBinding(*Object, PossessedActor != nullptr ? PossessedActor->GetActorLabel() : Object->GetName());
AActor* OwningActor = PossessedActor;
FGuid OwningObjectGuid = ObjectGuid;
if (!OwningActor)
{
// We can only add Object Bindings for actors to folders, but this function can be called on a component of an Actor.
// In this case, we attempt to find the Actor who owns the component and then look up the Binding Guid for that actor
// so that we add that actor to the folder as expected.
OwningActor = Object->GetTypedOuter<AActor>();
if (OwningActor)
{
OwningObjectGuid = FocusedMovieSceneSequence->FindPossessableObjectId(*OwningActor, PlaybackContext);
}
}
if (OwningActor)
{
GetHandleToObject(OwningActor);
}
// Some sources that create object bindings may want to group all of these objects together for organizations sake.
if (OwningActor && CreatedFolderName != NAME_None)
{
TArray<FName> SubfolderHierarchy;
if (OwningActor->GetFolderPath() != NAME_None)
{
TArray<FString> FolderPath;
OwningActor->GetFolderPath().ToString().ParseIntoArray(FolderPath, TEXT("/"));
for (FString FolderStr : FolderPath)
{
SubfolderHierarchy.Add(FName(*FolderStr));
}
}
// Add the desired sub-folder as the root of the hierarchy so that the Actor's World Outliner folder structure is replicated inside of the desired folder name.
// This has to come after the ParseIntoArray call as that will wipe the array.
SubfolderHierarchy.Insert(CreatedFolderName, 0);
UMovieSceneFolder* TailFolder = FSequencer::CreateFoldersRecursively(SubfolderHierarchy, 0, FocusedMovieScene, nullptr, FocusedMovieScene->GetRootFolders());
if (TailFolder)
{
TailFolder->AddChildObjectBinding(OwningObjectGuid);
}
// We have to build a new expansion state path since we created them in sub-folders.
// We have to recursively build an expansion state as well so that nestled objects get auto-expanded.
FString NewPath;
for (int32 Index = 0; Index < SubfolderHierarchy.Num(); Index++)
{
NewPath += SubfolderHierarchy[Index].ToString();
FocusedMovieScene->GetEditorData().ExpansionStates.FindOrAdd(NewPath) = FMovieSceneExpansionState(true);
// Expansion States are delimited by periods.
NewPath += TEXT(".");
}
}
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemAdded );
}
return ObjectGuid;
}
ISequencerObjectChangeListener& FSequencer::GetObjectChangeListener()
{
return *ObjectChangeListener;
}
void FSequencer::PossessPIEViewports(UObject* CameraObject, const EMovieSceneCameraCutParams& CameraCutParams)
{
UWorld* World = Cast<UWorld>(CachedPlaybackContext.Get());
if (!World || World->WorldType != EWorldType::PIE)
{
return;
}
APlayerController* PC = World->GetGameInstance()->GetFirstLocalPlayerController();
if (PC == nullptr)
{
return;
}
TWeakObjectPtr<APlayerController> WeakPC = PC;
auto FindViewTarget = [=](const FCachedViewTarget& In){ return In.PlayerController == WeakPC; };
// skip same view target
AActor* ViewTarget = PC->GetViewTarget();
// save the last view target so that it can be restored when the camera object is null
if (!PrePossessionViewTargets.ContainsByPredicate(FindViewTarget))
{
PrePossessionViewTargets.Add(FCachedViewTarget{ PC, ViewTarget });
}
UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(CameraObject);
if (CameraComponent && CameraComponent->GetOwner() != CameraObject)
{
CameraObject = CameraComponent->GetOwner();
}
if (CameraObject == ViewTarget)
{
if (CameraCutParams.bJumpCut)
{
if (PC->PlayerCameraManager)
{
PC->PlayerCameraManager->SetGameCameraCutThisFrame();
}
if (CameraComponent)
{
CameraComponent->NotifyCameraCut();
}
if (UMovieSceneMotionVectorSimulationSystem* MotionVectorSim = RootTemplateInstance.GetEntitySystemLinker()->FindSystem<UMovieSceneMotionVectorSimulationSystem>())
{
MotionVectorSim->SimulateAllTransforms();
}
}
return;
}
// skip unlocking if the current view target differs
AActor* UnlockIfCameraActor = Cast<AActor>(CameraCutParams.UnlockIfCameraObject);
// if unlockIfCameraActor is valid, release lock if currently locked to object
if (CameraObject == nullptr && UnlockIfCameraActor != nullptr && UnlockIfCameraActor != ViewTarget)
{
return;
}
// override the player controller's view target
AActor* CameraActor = Cast<AActor>(CameraObject);
// if the camera object is null, use the last view target so that it is restored to the state before the sequence takes control
if (CameraActor == nullptr)
{
if (const FCachedViewTarget* CachedTarget = PrePossessionViewTargets.FindByPredicate(FindViewTarget))
{
CameraActor = CachedTarget->ViewTarget.Get();
}
}
FViewTargetTransitionParams TransitionParams;
TransitionParams.BlendTime = FMath::Max(0.f, CameraCutParams.BlendTime);
PC->SetViewTarget(CameraActor, TransitionParams);
if (CameraComponent)
{
CameraComponent->NotifyCameraCut();
}
if (PC->PlayerCameraManager)
{
PC->PlayerCameraManager->bClientSimulatingViewTarget = (CameraActor != nullptr);
PC->PlayerCameraManager->SetGameCameraCutThisFrame();
}
if (UMovieSceneMotionVectorSimulationSystem* MotionVectorSim = RootTemplateInstance.GetEntitySystemLinker()->FindSystem<UMovieSceneMotionVectorSimulationSystem>())
{
MotionVectorSim->SimulateAllTransforms();
}
}
TSharedPtr<class ITimeSlider> FSequencer::GetTopTimeSliderWidget() const
{
return SequencerWidget->GetTopTimeSliderWidget();
}
void FSequencer::UpdateCameraCut(UObject* CameraObject, const EMovieSceneCameraCutParams& CameraCutParams)
{
OnCameraCutEvent.Broadcast(CameraObject, CameraCutParams.bJumpCut);
if (!IsPerspectiveViewportCameraCutEnabled())
{
return;
}
PossessPIEViewports(CameraObject, CameraCutParams);
// If the previous camera is null it means we are cutting from the editor camera, in which case
// we want to cache the current viewport's pre-animated info.
bool bShouldCachePreAnimatedViewportInfo = (
!bHasPreAnimatedInfo &&
(CameraObject == nullptr || CameraCutParams.PreviousCameraObject == nullptr) &&
!IsInSilentMode());
AActor* UnlockIfCameraActor = Cast<AActor>(CameraCutParams.UnlockIfCameraObject);
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if ((LevelVC == nullptr) || !LevelVC->AllowsCinematicControl())
{
continue;
}
if (CameraObject == nullptr && UnlockIfCameraActor != nullptr && !LevelVC->IsLockedToActor(UnlockIfCameraActor))
{
continue;
}
if (bShouldCachePreAnimatedViewportInfo)
{
PreAnimatedViewportLocation = LevelVC->GetViewLocation();
PreAnimatedViewportRotation = LevelVC->GetViewRotation();
PreAnimatedViewportFOV = LevelVC->ViewFOV;
bHasPreAnimatedInfo = true;
// We end-up only caching the first cinematic viewport's info, which means that
// if we are previewing the sequence on 2 different viewports, the second viewport
// will blend back to the same camera position as the first viewport, even if they
// started at different positions (which is very likely). It's a small downside to
// pay for a much simpler piece of code, and for a use-case that is frankly
// probably very uncommon.
bShouldCachePreAnimatedViewportInfo = false;
}
UpdatePreviewLevelViewportClientFromCameraCut(*LevelVC, CameraObject, CameraCutParams);
}
// Clear pre-animated info when we exit any sequencer camera.
if (CameraObject == nullptr && CameraCutParams.BlendTime < 0.f)
{
bHasPreAnimatedInfo = false;
}
}
void FSequencer::UpdateLevelViewportClientsActorLocks()
{
// Nothing to do if we are not editing level sequence, as these are the only kinds of sequences right now
// that have some aspect ratio constraints settings.
const ALevelSequenceActor* LevelSequenceActor = Cast<ALevelSequenceActor>(GetPlaybackClient());
if (LevelSequenceActor == nullptr)
{
return;
}
TOptional<EAspectRatioAxisConstraint> AspectRatioAxisConstraint;
if (LevelSequenceActor->CameraSettings.bOverrideAspectRatioAxisConstraint)
{
AspectRatioAxisConstraint = LevelSequenceActor->CameraSettings.AspectRatioAxisConstraint;
}
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC != nullptr)
{
// If there is an actor lock on an actor that turns out to be one of our cameras, set the
// aspect ratio axis constraint on it.
FLevelViewportActorLock& ActorLock = LevelVC->GetActorLock();
if (AActor* LockedActor = ActorLock.GetLockedActor())
{
if (CachedCameraActors.Find(LockedActor))
{
ActorLock.AspectRatioAxisConstraint = AspectRatioAxisConstraint;
}
}
// If we are in control of the entire viewport, also set the aspect ratio axis constraint.
if (IsPerspectiveViewportCameraCutEnabled())
{
FLevelViewportActorLock& CinematicLock = LevelVC->GetCinematicActorLock();
if (AActor* LockedActor = CinematicLock.GetLockedActor())
{
CinematicLock.AspectRatioAxisConstraint = AspectRatioAxisConstraint;
}
}
}
}
}
void FSequencer::GetCameraObjectBindings(TArray<FGuid>& OutBindingIDs)
{
CachedCameraActors.GenerateValueArray(OutBindingIDs);
}
void FSequencer::NotifyBindingsChanged()
{
ISequencer::NotifyBindingsChanged();
OnMovieSceneBindingsChangedDelegate.Broadcast();
}
void FSequencer::SetViewportSettings(const TMap<FViewportClient*, EMovieSceneViewportParams>& ViewportParamsMap)
{
if (!IsPerspectiveViewportPossessionEnabled())
{
return;
}
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC)
{
if (LevelVC->AllowsCinematicControl())
{
if (ViewportParamsMap.Contains(LevelVC))
{
const EMovieSceneViewportParams* ViewportParams = ViewportParamsMap.Find(LevelVC);
if (ViewportParams->SetWhichViewportParam & EMovieSceneViewportParams::SVP_FadeAmount)
{
LevelVC->FadeAmount = ViewportParams->FadeAmount;
LevelVC->bEnableFading = true;
}
if (ViewportParams->SetWhichViewportParam & EMovieSceneViewportParams::SVP_FadeColor)
{
LevelVC->FadeColor = ViewportParams->FadeColor.ToFColor(/*bSRGB=*/ true);
LevelVC->bEnableFading = true;
}
if (ViewportParams->SetWhichViewportParam & EMovieSceneViewportParams::SVP_ColorScaling)
{
LevelVC->bEnableColorScaling = ViewportParams->bEnableColorScaling;
LevelVC->ColorScale = ViewportParams->ColorScale;
}
}
}
else
{
LevelVC->bEnableFading = false;
LevelVC->bEnableColorScaling = false;
}
}
}
}
void FSequencer::GetViewportSettings(TMap<FViewportClient*, EMovieSceneViewportParams>& ViewportParamsMap) const
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC && LevelVC->AllowsCinematicControl())
{
EMovieSceneViewportParams ViewportParams;
ViewportParams.FadeAmount = LevelVC->FadeAmount;
ViewportParams.FadeColor = FLinearColor(LevelVC->FadeColor);
ViewportParams.ColorScale = LevelVC->ColorScale;
ViewportParamsMap.Add(LevelVC, ViewportParams);
}
}
}
EMovieScenePlayerStatus::Type FSequencer::GetPlaybackStatus() const
{
return PlaybackState;
}
void FSequencer::SetPlaybackStatus(EMovieScenePlayerStatus::Type InPlaybackStatus)
{
PlaybackState = InPlaybackStatus;
PauseOnFrame.Reset();
// Inform the renderer when Sequencer is in a 'paused' state for the sake of inter-frame effects
ESequencerState SequencerState = ESS_None;
if (InPlaybackStatus == EMovieScenePlayerStatus::Playing)
{
SequencerState = ESS_Playing;
}
else if (InPlaybackStatus == EMovieScenePlayerStatus::Stopped || InPlaybackStatus == EMovieScenePlayerStatus::Scrubbing || InPlaybackStatus == EMovieScenePlayerStatus::Stepping)
{
SequencerState = ESS_Paused;
}
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC && LevelVC->AllowsCinematicControl())
{
LevelVC->ViewState.GetReference()->SetSequencerState(SequencerState);
}
}
if (InPlaybackStatus == EMovieScenePlayerStatus::Playing)
{
if (Settings->GetCleanPlaybackMode())
{
CachedViewState.StoreViewState();
}
// override max frame rate
if (PlayPosition.GetEvaluationType() == EMovieSceneEvaluationType::FrameLocked)
{
if (!OldMaxTickRate.IsSet())
{
OldMaxTickRate = GEngine->GetMaxFPS();
}
GEngine->SetMaxFPS(1.f / PlayPosition.GetInputRate().AsInterval());
}
}
else
{
CachedViewState.RestoreViewState();
StopAutoscroll();
if (OldMaxTickRate.IsSet())
{
GEngine->SetMaxFPS(OldMaxTickRate.GetValue());
OldMaxTickRate.Reset();
}
ShuttleMultiplier = 0;
}
TimeController->PlayerStatusChanged(PlaybackState, GetGlobalTime());
}
void FSequencer::AddReferencedObjects( FReferenceCollector& Collector )
{
Collector.AddReferencedObject( CompiledDataManager );
Collector.AddReferencedObject( Settings );
if (UMovieSceneSequence* RootSequencePtr = RootSequence.Get())
{
Collector.AddReferencedObject( RootSequencePtr );
}
FMovieSceneRootEvaluationTemplateInstance::StaticStruct()->SerializeBin(Collector.GetVerySlowReferenceCollectorArchive(), &RootTemplateInstance);
}
FString FSequencer::GetReferencerName() const
{
return TEXT("FSequencer");
}
void FSequencer::ResetPerMovieSceneData()
{
using namespace UE::Sequencer;
//@todo Sequencer - We may want to preserve selections when moving between movie scenes
Selection.Empty();
// This will reinitialize all extensions to rebuild the view-model hierarchy
UMovieSceneSequence* CurrentSequence = GetFocusedMovieSceneSequence();
TSharedPtr<FSequenceModel> RootSequenceModel = ViewModel->GetRootModel().ImplicitCast();
RootSequenceModel->SetSequence(CurrentSequence, ActiveTemplateIDs.Top());
RefreshTree();
UpdateTimeBoundsToFocusedMovieScene();
SuppressAutoEvalSignature.Reset();
// @todo run through all tracks for new movie scene changes
// needed for audio track decompression
}
TSharedRef<SWidget> FSequencer::MakeTransportControls(bool bExtended)
{
FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::Get().LoadModuleChecked<FEditorWidgetsModule>( "EditorWidgets" );
FTransportControlArgs TransportControlArgs;
{
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(FOnMakeTransportWidget::CreateSP(this, &FSequencer::OnCreateTransportRecord)));
TransportControlArgs.OnBackwardEnd.BindSP( this, &FSequencer::OnJumpToStart );
TransportControlArgs.OnBackwardStep.BindSP( this, &FSequencer::OnStepBackward, FFrameNumber(1) );
TransportControlArgs.OnForwardPlay.BindSP( this, &FSequencer::OnPlayForward, true );
TransportControlArgs.OnBackwardPlay.BindSP( this, &FSequencer::OnPlayBackward, true );
TransportControlArgs.OnForwardStep.BindSP( this, &FSequencer::OnStepForward, FFrameNumber(1) );
TransportControlArgs.OnForwardEnd.BindSP( this, &FSequencer::OnJumpToEnd );
TransportControlArgs.OnGetPlaybackMode.BindSP( this, &FSequencer::GetPlaybackMode );
if(bExtended)
{
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(FOnMakeTransportWidget::CreateSP(this, &FSequencer::OnCreateTransportSetPlaybackStart)));
}
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(ETransportControlWidgetType::BackwardEnd));
if(bExtended)
{
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(FOnMakeTransportWidget::CreateSP(this, &FSequencer::OnCreateTransportJumpToPreviousKey)));
}
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(ETransportControlWidgetType::BackwardStep));
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(ETransportControlWidgetType::BackwardPlay));
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(ETransportControlWidgetType::ForwardPlay));
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(ETransportControlWidgetType::ForwardStep));
if(bExtended)
{
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(FOnMakeTransportWidget::CreateSP(this, &FSequencer::OnCreateTransportJumpToNextKey)));
}
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(ETransportControlWidgetType::ForwardEnd));
if(bExtended)
{
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(FOnMakeTransportWidget::CreateSP(this, &FSequencer::OnCreateTransportSetPlaybackEnd)));
}
TransportControlArgs.WidgetsToCreate.Add(FTransportControlWidget(FOnMakeTransportWidget::CreateSP(this, &FSequencer::OnCreateTransportLoopMode)));
TransportControlArgs.bAreButtonsFocusable = false;
}
return EditorWidgetsModule.CreateTransportControl( TransportControlArgs );
}
TSharedRef<SWidget> FSequencer::OnCreateTransportSetPlaybackStart()
{
FText SetPlaybackStartToolTip = FText::Format(LOCTEXT("SetPlayStart_Tooltip", "Set playback start to the current position ({0})"), FSequencerCommands::Get().SetStartPlaybackRange->GetInputText());
return SNew(SButton)
.OnClicked(this, &FSequencer::SetPlaybackStart)
.ToolTipText(SetPlaybackStartToolTip)
.ButtonStyle(FAppStyle::Get(), "Sequencer.Transport.SetPlayStart")
.ContentPadding(2.0f);
}
TSharedRef<SWidget> FSequencer::OnCreateTransportJumpToPreviousKey()
{
FText JumpToPreviousKeyToolTip = FText::Format(LOCTEXT("JumpToPreviousKey_Tooltip", "Jump to the previous key in the selected track(s) ({0})"), FSequencerCommands::Get().StepToPreviousKey->GetInputText());
return SNew(SButton)
.OnClicked(this, &FSequencer::JumpToPreviousKey)
.ToolTipText(JumpToPreviousKeyToolTip)
.ButtonStyle(FAppStyle::Get(), "Sequencer.Transport.JumpToPreviousKey")
.ContentPadding(2.0f);
}
TSharedRef<SWidget> FSequencer::OnCreateTransportJumpToNextKey()
{
FText JumpToNextKeyToolTip = FText::Format(LOCTEXT("JumpToNextKey_Tooltip", "Jump to the next key in the selected track(s) ({0})"), FSequencerCommands::Get().StepToNextKey->GetInputText());
return SNew(SButton)
.OnClicked(this, &FSequencer::JumpToNextKey)
.ToolTipText(JumpToNextKeyToolTip)
.ButtonStyle(FAppStyle::Get(), "Sequencer.Transport.JumpToNextKey")
.ContentPadding(2.0f);
}
TSharedRef<SWidget> FSequencer::OnCreateTransportSetPlaybackEnd()
{
FText SetPlaybackEndToolTip = FText::Format(LOCTEXT("SetPlayEnd_Tooltip", "Set playback end to the current position ({0})"), FSequencerCommands::Get().SetEndPlaybackRange->GetInputText());
return SNew(SButton)
.OnClicked(this, &FSequencer::SetPlaybackEnd)
.ToolTipText(SetPlaybackEndToolTip)
.ButtonStyle(FAppStyle::Get(), "Sequencer.Transport.SetPlayEnd")
.ContentPadding(2.0f);
}
TSharedRef<SWidget> FSequencer::OnCreateTransportLoopMode()
{
TSharedRef<SButton> LoopButton = SNew(SButton)
.OnClicked(this, &FSequencer::OnCycleLoopMode)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ToolTipText_Lambda([&]()
{
if (GetLoopMode() == ESequencerLoopMode::SLM_NoLoop)
{
return LOCTEXT("LoopModeNoLoop_Tooltip", "No looping");
}
else if (GetLoopMode() == ESequencerLoopMode::SLM_Loop)
{
return LOCTEXT("LoopModeLoop_Tooltip", "Loop playback range");
}
else
{
return LOCTEXT("LoopModeLoopSelectionRange_Tooltip", "Loop selection range");
}
})
.ContentPadding(2.0f);
TWeakPtr<SButton> WeakButton = LoopButton;
LoopButton->SetContent(SNew(SImage)
.Image_Lambda([&, WeakButton]()
{
if (GetLoopMode() == ESequencerLoopMode::SLM_NoLoop)
{
return WeakButton.IsValid() && WeakButton.Pin()->IsPressed() ?
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Loop.Disabled").Pressed :
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Loop.Disabled").Normal;
}
else if (GetLoopMode() == ESequencerLoopMode::SLM_Loop)
{
return WeakButton.IsValid() && WeakButton.Pin()->IsPressed() ?
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Loop.Enabled").Pressed :
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Loop.Enabled").Normal;
}
else
{
return WeakButton.IsValid() && WeakButton.Pin()->IsPressed() ?
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Loop.SelectionRange").Pressed :
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Loop.SelectionRange").Normal;
}
})
);
return LoopButton;
}
TSharedRef<SWidget> FSequencer::OnCreateTransportRecord()
{
TSharedRef<SButton> RecordButton = SNew(SButton)
.OnClicked(this, &FSequencer::OnRecord)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ToolTipText_Lambda([&]()
{
FText OutTooltipText;
if (OnGetCanRecord().IsBound())
{
OnGetCanRecord().Execute(OutTooltipText);
}
if (!OutTooltipText.IsEmpty())
{
return OutTooltipText;
}
else
{
return OnGetIsRecording().IsBound() && OnGetIsRecording().Execute() ? LOCTEXT("StopRecord_Tooltip", "Stop recording") : LOCTEXT("Record_Tooltip", "Start recording");
}
})
.Visibility_Lambda([&] { return HostCapabilities.bSupportsRecording && OnGetCanRecord().IsBound() ? EVisibility::Visible : EVisibility::Collapsed; })
.IsEnabled_Lambda([&] { FText OutErrorText; return OnGetCanRecord().IsBound() && OnGetCanRecord().Execute(OutErrorText); })
.ContentPadding(2.0f);
TWeakPtr<SButton> WeakButton = RecordButton;
RecordButton->SetContent(SNew(SImage)
.Image_Lambda([this, WeakButton]()
{
if (OnGetIsRecording().IsBound() && OnGetIsRecording().Execute())
{
return WeakButton.IsValid() && WeakButton.Pin()->IsPressed() ?
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Recording").Pressed :
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Recording").Normal;
}
return WeakButton.IsValid() && WeakButton.Pin()->IsPressed() ?
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Record").Pressed :
&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("Animation.Record").Normal;
})
.ColorAndOpacity_Lambda([this]()
{
if (OnGetIsRecording().IsBound() && OnGetIsRecording().Execute())
{
if (!RecordingAnimation.IsPlaying())
{
RecordingAnimation.Play(SequencerWidget.ToSharedRef(), true);
}
return FLinearColor(1.f, 1.f, 1.f, 0.2f + 0.8f * RecordingAnimation.GetLerp());
}
RecordingAnimation.Pause();
return FLinearColor::White;
})
);
TSharedRef<SHorizontalBox> RecordBox = SNew(SHorizontalBox);
RecordBox->AddSlot()
.AutoWidth()
[
RecordButton
];
RecordBox->AddSlot()
[
// Intentionally add some space to separate the record button so it's not easily pressed
SNew(SSpacer)
.Size(FVector2D(10.0f, 0.0f))
];
return RecordBox;
}
UObject* FSequencer::FindSpawnedObjectOrTemplate(const FGuid& BindingId)
{
TArrayView<TWeakObjectPtr<>> Objects = FindObjectsInCurrentSequence(BindingId);
if (Objects.Num())
{
return Objects[0].Get();
}
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if (!Sequence)
{
return nullptr;
}
UMovieScene* FocusedMovieScene = Sequence->GetMovieScene();
if (!FocusedMovieScene)
{
return nullptr;
}
FMovieScenePossessable* Possessable = FocusedMovieScene->FindPossessable(BindingId);
// If we're a possessable with a parent spawnable and we don't have the object, we look the object up within the default object of the spawnable
if (Possessable && Possessable->GetParent().IsValid())
{
// If we're a spawnable and we don't have the object, use the default object to build up the track menu
FMovieSceneSpawnable* ParentSpawnable = FocusedMovieScene->FindSpawnable(Possessable->GetParent());
if (ParentSpawnable)
{
UObject* ParentObject = ParentSpawnable->GetObjectTemplate();
if (ParentObject)
{
for (UObject* Obj : Sequence->LocateBoundObjects(BindingId, ParentObject))
{
return Obj;
}
}
}
}
// If we're a spawnable and we don't have the object, use the default object to build up the track menu
else if (FMovieSceneSpawnable* Spawnable = FocusedMovieScene->FindSpawnable(BindingId))
{
return Spawnable->GetObjectTemplate();
}
return nullptr;
}
void
FSequencer::FCachedViewState::StoreViewState()
{
bValid = true;
bIsViewportUIHidden = GLevelEditorModeTools().IsViewportUIHidden();
GLevelEditorModeTools().SetHideViewportUI(!GLevelEditorModeTools().IsViewportUIHidden());
GameViewStates.Empty();
for (int32 ViewIndex = 0; ViewIndex < GEditor->GetLevelViewportClients().Num(); ++ViewIndex)
{
FLevelEditorViewportClient* LevelVC = GEditor->GetLevelViewportClients()[ViewIndex];
if (LevelVC && LevelVC->AllowsCinematicControl())
{
GameViewStates.Add(TPair<int32, bool>(ViewIndex, LevelVC->IsInGameView()));
LevelVC->SetGameView(true);
}
}
}
void
FSequencer::FCachedViewState::RestoreViewState()
{
if (!bValid)
{
return;
}
bValid = false;
GLevelEditorModeTools().SetHideViewportUI(bIsViewportUIHidden);
for (int32 Index = 0; Index < GameViewStates.Num(); ++Index)
{
int32 ViewIndex = GameViewStates[Index].Key;
if (GEditor->GetLevelViewportClients().IsValidIndex(ViewIndex))
{
FLevelEditorViewportClient* LevelVC = GEditor->GetLevelViewportClients()[ViewIndex];
if (LevelVC && LevelVC->AllowsCinematicControl())
{
LevelVC->SetGameView(GameViewStates[Index].Value);
}
}
}
GameViewStates.Empty();
}
FReply FSequencer::OnPlay(bool bTogglePlay)
{
if( PlaybackState == EMovieScenePlayerStatus::Playing && bTogglePlay )
{
Pause();
}
else
{
TRange<FFrameNumber> TimeBounds = GetTimeBounds();
FFrameNumber MinInclusiveTime = UE::MovieScene::DiscreteInclusiveLower(TimeBounds);
FFrameNumber MaxInclusiveTime = UE::MovieScene::DiscreteExclusiveUpper(TimeBounds) - 1;
if (GetLocalTime().Time <= MinInclusiveTime || GetLocalTime().Time >= MaxInclusiveTime)
{
FFrameTime NewGlobalTime = (PlaybackSpeed > 0 ? MinInclusiveTime : MaxInclusiveTime) * RootToLocalTransform.InverseFromWarp(RootToLocalLoopCounter);
SetGlobalTime(NewGlobalTime);
}
SetPlaybackStatus(EMovieScenePlayerStatus::Playing);
// Make sure Slate ticks during playback
SequencerWidget->RegisterActiveTimerForPlayback();
OnPlayDelegate.Broadcast();
}
return FReply::Handled();
}
FReply FSequencer::OnRecord()
{
OnRecordDelegate.Broadcast();
return FReply::Handled();
}
FReply FSequencer::OnPlayForward(bool bTogglePlay)
{
if (PlaybackSpeed < 0)
{
PlaybackSpeed = -PlaybackSpeed;
if (PlaybackState != EMovieScenePlayerStatus::Playing)
{
OnPlay(false);
}
}
else
{
OnPlay(bTogglePlay);
}
return FReply::Handled();
}
FReply FSequencer::OnPlayBackward(bool bTogglePlay)
{
if (PlaybackSpeed > 0)
{
PlaybackSpeed = -PlaybackSpeed;
if (PlaybackState != EMovieScenePlayerStatus::Playing)
{
OnPlay(false);
}
}
else
{
OnPlay(bTogglePlay);
}
return FReply::Handled();
}
FReply FSequencer::OnStepForward(FFrameNumber Increment)
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
FFrameRate DisplayRate = GetFocusedDisplayRate();
FQualifiedFrameTime CurrentTime = GetLocalTime();
FFrameTime NewPosition = FFrameRate::TransformTime(CurrentTime.ConvertTo(DisplayRate).FloorToFrame() + Increment, DisplayRate, CurrentTime.Rate);
SetLocalTime(NewPosition, ESnapTimeMode::STM_Interval);
return FReply::Handled();
}
FReply FSequencer::OnStepBackward(FFrameNumber Increment)
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
FFrameRate DisplayRate = GetFocusedDisplayRate();
FQualifiedFrameTime CurrentTime = GetLocalTime();
FFrameTime NewPosition = FFrameRate::TransformTime(CurrentTime.ConvertTo(DisplayRate).FloorToFrame() - Increment, DisplayRate, CurrentTime.Rate);
SetLocalTime(NewPosition, ESnapTimeMode::STM_Interval);
return FReply::Handled();
}
FReply FSequencer::OnJumpToStart()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
SetLocalTime(UE::MovieScene::DiscreteInclusiveLower(GetTimeBounds()), ESnapTimeMode::STM_None);
return FReply::Handled();
}
FReply FSequencer::OnJumpToEnd()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
const bool bInsetDisplayFrame = ScrubStyle == ESequencerScrubberStyle::FrameBlock && Settings->GetSnapPlayTimeToInterval() && Settings->GetIsSnapEnabled();
FFrameRate LocalResolution = GetFocusedTickResolution();
FFrameRate DisplayRate = GetFocusedDisplayRate();
// Calculate an offset from the end to go to. If they have snapping on (and the scrub style is a block) the last valid frame is represented as one
// whole display rate frame before the end, otherwise we just subtract a single frame which matches the behavior of hitting play and letting it run to the end.
FFrameTime OneFrame = bInsetDisplayFrame ? FFrameRate::TransformTime(FFrameTime(1), DisplayRate, LocalResolution) : FFrameTime(1);
FFrameTime NewTime = UE::MovieScene::DiscreteExclusiveUpper(GetTimeBounds()) - OneFrame;
SetLocalTime(NewTime, ESnapTimeMode::STM_None);
return FReply::Handled();
}
FReply FSequencer::OnCycleLoopMode()
{
ESequencerLoopMode LoopMode = Settings->GetLoopMode();
if (LoopMode == ESequencerLoopMode::SLM_NoLoop)
{
Settings->SetLoopMode(ESequencerLoopMode::SLM_Loop);
}
else if (LoopMode == ESequencerLoopMode::SLM_Loop && !GetSelectionRange().IsEmpty())
{
Settings->SetLoopMode(ESequencerLoopMode::SLM_LoopSelectionRange);
}
else if (LoopMode == ESequencerLoopMode::SLM_LoopSelectionRange || GetSelectionRange().IsEmpty())
{
Settings->SetLoopMode(ESequencerLoopMode::SLM_NoLoop);
}
return FReply::Handled();
}
FReply FSequencer::SetPlaybackEnd()
{
const UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
if (FocusedSequence)
{
FFrameNumber CurrentFrame = GetLocalTime().Time.FloorToFrame();
TRange<FFrameNumber> CurrentRange = FocusedSequence->GetMovieScene()->GetPlaybackRange();
if (CurrentFrame >= UE::MovieScene::DiscreteInclusiveLower(CurrentRange))
{
CurrentRange.SetUpperBoundValue(CurrentFrame);
SetPlaybackRange(CurrentRange);
}
}
return FReply::Handled();
}
FReply FSequencer::SetPlaybackStart()
{
const UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
if (FocusedSequence)
{
FFrameNumber CurrentFrame = GetLocalTime().Time.FloorToFrame();
TRange<FFrameNumber> CurrentRange = FocusedSequence->GetMovieScene()->GetPlaybackRange();
if (CurrentFrame < UE::MovieScene::DiscreteExclusiveUpper(CurrentRange))
{
CurrentRange.SetLowerBound(CurrentFrame);
SetPlaybackRange(CurrentRange);
}
}
return FReply::Handled();
}
FReply FSequencer::JumpToPreviousKey()
{
if (Selection.GetSelectedOutlinerItems().Num())
{
GetKeysFromSelection(SelectedKeyCollection, SMALL_NUMBER);
}
else
{
GetAllKeys(SelectedKeyCollection, SMALL_NUMBER);
}
if (SelectedKeyCollection.IsValid())
{
FFrameNumber FrameNumber = GetLocalTime().Time.FloorToFrame();
TOptional<FFrameNumber> NewTime = SelectedKeyCollection->GetNextKey(FrameNumber, EFindKeyDirection::Backwards);
if (NewTime.IsSet())
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
// Ensure the time is in the current view
FFrameRate LocalResolution = GetFocusedTickResolution();
ScrollIntoView(NewTime.GetValue() / LocalResolution);
SetLocalTimeDirectly(NewTime.GetValue());
}
}
return FReply::Handled();
}
FReply FSequencer::JumpToNextKey()
{
if (Selection.GetSelectedOutlinerItems().Num())
{
GetKeysFromSelection(SelectedKeyCollection, SMALL_NUMBER);
}
else
{
GetAllKeys(SelectedKeyCollection, SMALL_NUMBER);
}
if (SelectedKeyCollection.IsValid())
{
FFrameNumber FrameNumber = GetLocalTime().Time.FloorToFrame();
TOptional<FFrameNumber> NewTime = SelectedKeyCollection->GetNextKey(FrameNumber, EFindKeyDirection::Forwards);
if (NewTime.IsSet())
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
// Ensure the time is in the current view
FFrameRate LocalResolution = GetFocusedTickResolution();
ScrollIntoView(NewTime.GetValue() / LocalResolution);
SetLocalTimeDirectly(NewTime.GetValue());
}
}
return FReply::Handled();
}
ESequencerLoopMode FSequencer::GetLoopMode() const
{
return Settings->GetLoopMode();
}
void FSequencer::SetLocalTimeLooped(FFrameTime NewLocalTime)
{
TOptional<EMovieScenePlayerStatus::Type> NewPlaybackStatus;
const FMovieSceneSequenceTransform LocalToRootTransform = RootToLocalTransform.InverseFromWarp(RootToLocalLoopCounter);
FFrameTime NewGlobalTime = NewLocalTime * LocalToRootTransform;
TRange<FFrameNumber> TimeBounds = GetTimeBounds();
bool bResetPosition = false;
FFrameRate LocalTickResolution = GetFocusedTickResolution();
FFrameRate RootTickResolution = GetRootTickResolution();
FFrameNumber MinInclusiveTime = UE::MovieScene::DiscreteInclusiveLower(TimeBounds);
FFrameNumber MaxInclusiveTime = UE::MovieScene::DiscreteExclusiveUpper(TimeBounds)-1;
bool bHasJumped = false;
bool bRestarted = false;
if (PauseOnFrame.IsSet() && ((PlaybackSpeed > 0 && NewLocalTime > PauseOnFrame.GetValue()) || (PlaybackSpeed < 0 && NewLocalTime < PauseOnFrame.GetValue())))
{
NewGlobalTime = PauseOnFrame.GetValue() * LocalToRootTransform;
PauseOnFrame.Reset();
bResetPosition = true;
NewPlaybackStatus = EMovieScenePlayerStatus::Stopped;
}
else if (GetLoopMode() == ESequencerLoopMode::SLM_Loop || GetLoopMode() == ESequencerLoopMode::SLM_LoopSelectionRange)
{
const UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
if (FocusedSequence)
{
if (NewLocalTime < MinInclusiveTime || NewLocalTime > MaxInclusiveTime)
{
NewGlobalTime = (PlaybackSpeed > 0 ? MinInclusiveTime : MaxInclusiveTime) * LocalToRootTransform;
bResetPosition = true;
bHasJumped = true;
}
}
}
else
{
TRange<double> WorkingRange = GetClampRange();
bool bReachedEnd = false;
if (PlaybackSpeed > 0)
{
bReachedEnd = GetLocalTime().Time <= MaxInclusiveTime && NewLocalTime >= MaxInclusiveTime;
}
else
{
bReachedEnd = GetLocalTime().Time >= MinInclusiveTime && NewLocalTime <= MinInclusiveTime;
}
// Stop if we hit the playback range end
if (bReachedEnd)
{
NewGlobalTime = (PlaybackSpeed > 0 ? MaxInclusiveTime : MinInclusiveTime) * LocalToRootTransform;
NewPlaybackStatus = EMovieScenePlayerStatus::Stopped;
}
}
// Ensure the time is in the current view - must occur before the time cursor changes
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (IsAutoScrollEnabled())
{
ScrollIntoView((NewGlobalTime * RootToLocalTransform) / RootTickResolution);
}
FFrameTime NewPlayPosition = ConvertFrameTime(NewGlobalTime, RootTickResolution, PlayPosition.GetInputRate());
// Reset the play cursor if we're looping or have otherwise jumpted to a new position in the sequence
if (bResetPosition)
{
PlayPosition.Reset(NewPlayPosition);
TimeController->Reset(FQualifiedFrameTime(NewGlobalTime, RootTickResolution));
}
// Evaluate the sequence
FMovieSceneEvaluationRange EvalRange = PlayPosition.PlayTo(NewPlayPosition);
EvaluateInternal(EvalRange, bHasJumped);
// Set the playback status if we need to
if (NewPlaybackStatus.IsSet())
{
SetPlaybackStatus(NewPlaybackStatus.GetValue());
// Evaluate the sequence with the new status
EvaluateInternal(EvalRange);
}
}
EPlaybackMode::Type FSequencer::GetPlaybackMode() const
{
if (PlaybackState == EMovieScenePlayerStatus::Playing)
{
if (PlaybackSpeed > 0)
{
return EPlaybackMode::PlayingForward;
}
else
{
return EPlaybackMode::PlayingReverse;
}
}
return EPlaybackMode::Stopped;
}
void FSequencer::UpdateTimeBoundsToFocusedMovieScene()
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
FQualifiedFrameTime CurrentTime = GetLocalTime();
// Set the view range to:
// 1. The moviescene view range
// 2. The moviescene playback range
// 3. Some sensible default
TRange<double> NewRange = FocusedMovieScene->GetEditorData().GetViewRange();
if (NewRange.IsEmpty() || NewRange.IsDegenerate())
{
NewRange = FocusedMovieScene->GetPlaybackRange() / CurrentTime.Rate;
}
if (NewRange.IsEmpty() || NewRange.IsDegenerate())
{
NewRange = TRange<double>(0.0, 5.0);
}
// Set the view range to the new range
SetViewRange(NewRange, EViewRangeInterpolation::Immediate);
}
TRange<FFrameNumber> FSequencer::GetTimeBounds() const
{
const UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
if(!FocusedSequence)
{
return TRange<FFrameNumber>( -100000, 100000 );
}
if (GetLoopMode() == ESequencerLoopMode::SLM_LoopSelectionRange)
{
if (!GetSelectionRange().IsEmpty())
{
return GetSelectionRange();
}
}
if (Settings->ShouldEvaluateSubSequencesInIsolation() || ActiveTemplateIDs.Num() == 1)
{
return FocusedSequence->GetMovieScene()->GetPlaybackRange();
}
return SubSequenceRange;
}
void FSequencer::SetViewRange(TRange<double> NewViewRange, EViewRangeInterpolation Interpolation)
{
if (!ensure(NewViewRange.HasUpperBound() && NewViewRange.HasLowerBound() && !NewViewRange.IsDegenerate()))
{
return;
}
const float AnimationLengthSeconds = Interpolation == EViewRangeInterpolation::Immediate ? 0.f : 0.1f;
if (AnimationLengthSeconds != 0.f)
{
if (ZoomAnimation.GetCurve(0).DurationSeconds != AnimationLengthSeconds)
{
ZoomAnimation = FCurveSequence();
ZoomCurve = ZoomAnimation.AddCurve(0.f, AnimationLengthSeconds, ECurveEaseFunction::QuadIn);
}
if (!ZoomAnimation.IsPlaying())
{
LastViewRange = TargetViewRange;
ZoomAnimation.Play( SequencerWidget.ToSharedRef() );
}
TargetViewRange = NewViewRange;
}
else
{
TargetViewRange = LastViewRange = NewViewRange;
ZoomAnimation.JumpToEnd();
}
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (FocusedMovieSequence != nullptr)
{
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (FocusedMovieScene != nullptr)
{
FMovieSceneEditorData& EditorData = FocusedMovieScene->GetEditorData();
EditorData.ViewStart = TargetViewRange.GetLowerBoundValue();
EditorData.ViewEnd = TargetViewRange.GetUpperBoundValue();
// Always ensure the working range is big enough to fit the view range
EditorData.WorkStart = FMath::Min(TargetViewRange.GetLowerBoundValue(), EditorData.WorkStart);
EditorData.WorkEnd = FMath::Max(TargetViewRange.GetUpperBoundValue(), EditorData.WorkEnd);
}
}
}
void FSequencer::OnClampRangeChanged( TRange<double> NewClampRange )
{
if (!NewClampRange.IsEmpty())
{
FMovieSceneEditorData& EditorData = GetFocusedMovieSceneSequence()->GetMovieScene()->GetEditorData();
EditorData.WorkStart = NewClampRange.GetLowerBoundValue();
EditorData.WorkEnd = NewClampRange.GetUpperBoundValue();
}
}
FFrameNumber FSequencer::OnGetNearestKey(FFrameTime InTime, ENearestKeyOption NearestKeyOption)
{
const FFrameNumber CurrentTime = InTime.FloorToFrame();
if (EnumHasAnyFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchAllTracks))
{
GetAllKeys(SelectedKeyCollection, SMALL_NUMBER);
}
else
{
GetKeysFromSelection(SelectedKeyCollection, SMALL_NUMBER);
}
TOptional<FFrameNumber> NearestTime;
if (EnumHasAnyFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchKeys) && SelectedKeyCollection.IsValid())
{
TOptional<FFrameNumber> NearestKeyTime;
TRange<FFrameNumber> FindRangeBackwards(TRangeBound<FFrameNumber>::Open(), CurrentTime);
TOptional<FFrameNumber> NewTimeBackwards = SelectedKeyCollection->FindFirstKeyInRange(FindRangeBackwards, EFindKeyDirection::Backwards);
TRange<FFrameNumber> FindRangeForwards(CurrentTime, TRangeBound<FFrameNumber>::Open());
TOptional<FFrameNumber> NewTimeForwards = SelectedKeyCollection->FindFirstKeyInRange(FindRangeForwards, EFindKeyDirection::Forwards);
if (NewTimeForwards.IsSet())
{
if (NewTimeBackwards.IsSet())
{
if (FMath::Abs(NewTimeForwards.GetValue() - CurrentTime) < FMath::Abs(NewTimeBackwards.GetValue() - CurrentTime))
{
NearestKeyTime = NewTimeForwards.GetValue();
}
else
{
NearestKeyTime = NewTimeBackwards.GetValue();
}
}
else
{
NearestKeyTime = NewTimeForwards.GetValue();
}
}
else if (NewTimeBackwards.IsSet())
{
NearestKeyTime = NewTimeBackwards.GetValue();
}
if (NearestKeyTime.IsSet())
{
NearestTime = NearestKeyTime.GetValue();
}
}
if (EnumHasAnyFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchSections) && SelectedKeyCollection.IsValid())
{
TOptional<FFrameNumber> NearestSectionTime;
TRange<FFrameNumber> FindRangeBackwards(TRangeBound<FFrameNumber>::Open(), CurrentTime);
TOptional<FFrameNumber> NewTimeBackwards = SelectedKeyCollection->FindFirstSectionKeyInRange(FindRangeBackwards, EFindKeyDirection::Backwards);
TRange<FFrameNumber> FindRangeForwards(CurrentTime, TRangeBound<FFrameNumber>::Open());
TOptional<FFrameNumber> NewTimeForwards = SelectedKeyCollection->FindFirstSectionKeyInRange(FindRangeForwards, EFindKeyDirection::Forwards);
if (NewTimeForwards.IsSet())
{
if (NewTimeBackwards.IsSet())
{
if (FMath::Abs(NewTimeForwards.GetValue() - CurrentTime) < FMath::Abs(NewTimeBackwards.GetValue() - CurrentTime))
{
NearestSectionTime = NewTimeForwards.GetValue();
}
else
{
NearestSectionTime = NewTimeBackwards.GetValue();
}
}
else
{
NearestSectionTime = NewTimeForwards.GetValue();
}
}
else if (NewTimeBackwards.IsSet())
{
NearestSectionTime = NewTimeBackwards.GetValue();
}
if (NearestSectionTime.IsSet())
{
if (!NearestTime.IsSet())
{
NearestTime = NearestSectionTime.GetValue();
}
else if (FMath::Abs(NearestSectionTime.GetValue() - CurrentTime) < FMath::Abs(NearestTime.GetValue() - CurrentTime))
{
NearestTime = NearestSectionTime.GetValue();
}
}
}
if (EnumHasAnyFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchMarkers))
{
TArray<FMovieSceneMarkedFrame> MarkedFrames = GetMarkedFrames();
for (const FMovieSceneMarkedFrame& MarkedFrame : MarkedFrames)
{
if (!NearestTime.IsSet())
{
NearestTime = MarkedFrame.FrameNumber;
}
else if (FMath::Abs(MarkedFrame.FrameNumber - CurrentTime) < FMath::Abs(NearestTime.GetValue() - CurrentTime))
{
NearestTime = MarkedFrame.FrameNumber;
}
}
TArray<FMovieSceneMarkedFrame> GlobalMarkedFrames = GetGlobalMarkedFrames();
for (const FMovieSceneMarkedFrame& GlobalMarkedFrame : GlobalMarkedFrames)
{
FFrameNumber Diff = FMath::Abs(GlobalMarkedFrame.FrameNumber - CurrentTime);
if (!NearestTime.IsSet())
{
NearestTime = GlobalMarkedFrame.FrameNumber;
}
else if (FMath::Abs(GlobalMarkedFrame.FrameNumber - CurrentTime) < FMath::Abs(NearestTime.GetValue() - CurrentTime))
{
NearestTime = GlobalMarkedFrame.FrameNumber;
}
}
}
return NearestTime.IsSet() ? NearestTime.GetValue() : CurrentTime;
}
void FSequencer::OnScrubPositionChanged( FFrameTime NewScrubPosition, bool bScrubbing , bool bEvaluate)
{
if (PlaybackState == EMovieScenePlayerStatus::Scrubbing)
{
if (!bScrubbing)
{
OnEndScrubbing();
}
else if (IsAutoScrollEnabled())
{
UpdateAutoScroll(NewScrubPosition / GetFocusedTickResolution());
// When scrubbing, we animate auto-scrolled scrub position in Tick()
if (AutoscrubOffset.IsSet())
{
return;
}
}
}
if (!bScrubbing && FSlateApplication::Get().GetModifierKeys().IsShiftDown())
{
AutoScrubToTime(NewScrubPosition);
}
else
{
SetLocalTimeDirectly(NewScrubPosition, bEvaluate);
}
}
void FSequencer::OnBeginScrubbing()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Scrubbing);
SequencerWidget->RegisterActiveTimerForPlayback();
LocalLoopIndexOnBeginScrubbing = GetLocalLoopIndex();
LocalLoopIndexOffsetDuringScrubbing = 0;
OnBeginScrubbingDelegate.Broadcast();
}
void FSequencer::OnEndScrubbing()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
AutoscrubOffset.Reset();
StopAutoscroll();
LocalLoopIndexOnBeginScrubbing = FMovieSceneTimeWarping::InvalidWarpCount;
LocalLoopIndexOffsetDuringScrubbing = 0;
OnEndScrubbingDelegate.Broadcast();
}
void FSequencer::OnPlaybackRangeBeginDrag()
{
GEditor->BeginTransaction(LOCTEXT("SetPlaybackRange_Transaction", "Set Playback Range"));
}
void FSequencer::OnPlaybackRangeEndDrag()
{
GEditor->EndTransaction();
}
void FSequencer::OnSelectionRangeBeginDrag()
{
GEditor->BeginTransaction(LOCTEXT("SetSelectionRange_Transaction", "Set Selection Range"));
}
void FSequencer::OnSelectionRangeEndDrag()
{
GEditor->EndTransaction();
}
void FSequencer::OnMarkBeginDrag()
{
GEditor->BeginTransaction(LOCTEXT("SetMark_Transaction", "Set Mark"));
}
void FSequencer::OnMarkEndDrag()
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;
if (OwnerMovieScene)
{
OwnerMovieScene->SortMarkedFrames();
}
GEditor->EndTransaction();
}
FString FSequencer::GetFrameTimeText() const
{
FMovieSceneSequenceTransform RootToParentChainTransform = RootToLocalTransform;
if (ScrubPositionParent.IsSet())
{
if (ScrubPositionParent.GetValue() == MovieSceneSequenceID::Root)
{
RootToParentChainTransform = FMovieSceneSequenceTransform();
}
else if (const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(RootTemplateInstance.GetCompiledDataID()))
{
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy->AllSubSequenceData())
{
if (Pair.Key == ScrubPositionParent.GetValue())
{
RootToParentChainTransform = Pair.Value.RootToSequenceTransform;
break;
}
}
}
}
const FFrameRate FocusedResolution = GetFocusedTickResolution();
const FFrameTime CurrentPosition = PlayPosition.GetCurrentPosition();
const FFrameTime RootTime = ConvertFrameTime(CurrentPosition, PlayPosition.GetInputRate(), PlayPosition.GetOutputRate());
const FFrameTime LocalTime = RootTime * RootToParentChainTransform;
return GetNumericTypeInterface()->ToString(LocalTime.GetFrame().Value);
}
FMovieSceneSequenceID FSequencer::GetScrubPositionParent() const
{
if (ScrubPositionParent.IsSet())
{
return ScrubPositionParent.GetValue();
}
return MovieSceneSequenceID::Invalid;
}
TArray<FMovieSceneSequenceID> FSequencer::GetScrubPositionParentChain() const
{
TArray<FMovieSceneSequenceID> ParentChain;
for (FMovieSceneSequenceID SequenceID : ActiveTemplateIDs)
{
ParentChain.Add(SequenceID);
}
return ParentChain;
}
void FSequencer::OnScrubPositionParentChanged(FMovieSceneSequenceID InScrubPositionParent)
{
ScrubPositionParent = InScrubPositionParent;
}
void FSequencer::StartAutoscroll(float UnitsPerS)
{
AutoscrollOffset = UnitsPerS;
}
void FSequencer::StopAutoscroll()
{
AutoscrollOffset.Reset();
AutoscrubOffset.Reset();
}
void FSequencer::OnToggleAutoScroll()
{
Settings->SetAutoScrollEnabled(!Settings->GetAutoScrollEnabled());
}
bool FSequencer::IsAutoScrollEnabled() const
{
return Settings->GetAutoScrollEnabled();
}
void FSequencer::FindInContentBrowser()
{
if (GetFocusedMovieSceneSequence())
{
TArray<UObject*> ObjectsToFocus;
ObjectsToFocus.Add(GetCurrentAsset());
GEditor->SyncBrowserToObjects(ObjectsToFocus);
}
}
UObject* FSequencer::GetCurrentAsset() const
{
// For now we find the asset by looking at the root movie scene's outer.
// @todo: this may need refining if/when we support editing movie scene instances
return GetFocusedMovieSceneSequence()->GetMovieScene()->GetOuter();
}
bool FSequencer::IsReadOnly() const
{
return bReadOnly || (GetFocusedMovieSceneSequence() && GetFocusedMovieSceneSequence()->GetMovieScene()->IsReadOnly());
}
FGuid FSequencer::AddSpawnable(UObject& Object, UActorFactory* ActorFactory)
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if (!Sequence->AllowsSpawnableObjects())
{
return FGuid();
}
// Grab the MovieScene that is currently focused. We'll add our Blueprint as an inner of the
// MovieScene asset.
UMovieScene* OwnerMovieScene = Sequence->GetMovieScene();
TValueOrError<FNewSpawnable, FText> Result = SpawnRegister->CreateNewSpawnableType(Object, *OwnerMovieScene, ActorFactory);
if (!Result.IsValid())
{
FNotificationInfo Info(Result.GetError());
Info.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(Info);
return FGuid();
}
FNewSpawnable& NewSpawnable = Result.GetValue();
NewSpawnable.Name = MovieSceneHelpers::MakeUniqueSpawnableName(OwnerMovieScene, NewSpawnable.Name);
FGuid NewGuid = OwnerMovieScene->AddSpawnable(NewSpawnable.Name, *NewSpawnable.ObjectTemplate);
ForceEvaluate();
return NewGuid;
}
FGuid FSequencer::MakeNewSpawnable( UObject& Object, UActorFactory* ActorFactory, bool bSetupDefaults )
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return FGuid();
}
// @todo sequencer: Undo doesn't seem to be working at all
const FScopedTransaction Transaction( LOCTEXT("UndoAddingObject", "Add Object to MovieScene") );
FGuid NewGuid = AddSpawnable(Object, ActorFactory);
if (!NewGuid.IsValid())
{
return FGuid();
}
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
if (SelectedParentFolders.Num() > 0)
{
SelectedParentFolders[0]->AddChildObjectBinding(NewGuid);
}
FMovieSceneSpawnable* Spawnable = GetFocusedMovieSceneSequence()->GetMovieScene()->FindSpawnable(NewGuid);
if (!Spawnable)
{
return FGuid();
}
// Spawn the object so we can position it correctly, it's going to get spawned anyway since things default to spawned.
UObject* SpawnedObject = SpawnRegister->SpawnObject(NewGuid, *MovieScene, ActiveTemplateIDs.Top(), *this);
if (bSetupDefaults)
{
FTransformData TransformData;
SpawnRegister->SetupDefaultsForSpawnable(SpawnedObject, Spawnable->GetGuid(), TransformData, AsShared(), Settings);
}
return NewGuid;
}
void FSequencer::AddSubSequence(UMovieSceneSequence* Sequence)
{
// @todo Sequencer - sub-moviescenes This should be moved to the sub-moviescene editor
// Grab the MovieScene that is currently focused. This is the movie scene that will contain the sub-moviescene
UMovieScene* OwnerMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (OwnerMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
// @todo sequencer: Undo doesn't seem to be working at all
const FScopedTransaction Transaction( LOCTEXT("UndoAddingObject", "Add Object to MovieScene") );
OwnerMovieScene->Modify();
UMovieSceneSubTrack* SubTrack = OwnerMovieScene->AddMasterTrack<UMovieSceneSubTrack>();
FFrameNumber Duration = ConvertFrameTime(
Sequence->GetMovieScene()->GetPlaybackRange().Size<FFrameNumber>(),
Sequence->GetMovieScene()->GetTickResolution(),
OwnerMovieScene->GetTickResolution()).FloorToFrame();
SubTrack->AddSequence(Sequence, GetLocalTime().Time.FloorToFrame(), Duration.Value);
}
bool FSequencer::OnHandleAssetDropped(UObject* DroppedAsset, const FGuid& TargetObjectGuid)
{
bool bWasConsumed = false;
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
bool bWasHandled = TrackEditors[i]->HandleAssetAdded(DroppedAsset, TargetObjectGuid);
if (bWasHandled)
{
// @todo Sequencer - This will crash if multiple editors try to handle a single asset
// Should we allow this? How should it consume then?
// gmp 10/7/2015: the user should be presented with a dialog asking what kind of track they want to create
check(!bWasConsumed);
bWasConsumed = true;
}
}
return bWasConsumed;
}
bool FSequencer::OnRequestNodeDeleted( TSharedRef<FViewModel> NodeToBeDeleted, const bool bKeepState )
{
using namespace UE::Sequencer;
using namespace UE::MovieScene;
// Find out which moviescene this is in
TViewModelPtr<FSequenceModel> OwnerModel = NodeToBeDeleted->FindAncestorOfType<FSequenceModel>();
if (!OwnerModel)
{
return false;
}
UMovieScene* OwnerMovieScene = OwnerModel->GetMovieScene();
if (OwnerMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return false;
}
if (bKeepState)
{
FMovieSceneSequenceID SequenceID = OwnerModel->GetSequenceID();
for (const TViewModelPtr<IObjectBindingExtension>& ObjectBinding :
NodeToBeDeleted->GetDescendantsOfType<IObjectBindingExtension>(true, EViewModelListType::Outliner))
{
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(ObjectBinding->GetObjectGuid(), SequenceID))
{
TArray<UObject*> SubObjects;
GetObjectsWithOuter(WeakObject.Get(), SubObjects);
PreAnimatedState.DiscardAndRemoveEntityTokensForObject(*WeakObject.Get());
for (UObject* SubObject : SubObjects)
{
if (SubObject)
{
PreAnimatedState.DiscardAndRemoveEntityTokensForObject(*SubObject);
}
}
}
}
}
if (IDeletableExtension* Deletable = NodeToBeDeleted->CastThis<IDeletableExtension>())
{
Deletable->Delete();
return true;
}
return false;
}
bool FSequencer::MatchesContext(const FTransactionContext& InContext, const TArray<TPair<UObject*, FTransactionObjectEvent>>& TransactionObjects) const
{
// Check if we care about the undo/redo
for (const TPair<UObject*, FTransactionObjectEvent>& TransactionObjectPair : TransactionObjects)
{
if (TransactionObjectPair.Value.HasPendingKillChange())
{
return true;
}
UObject* Object = TransactionObjectPair.Key;
while (Object != nullptr)
{
if (Object->GetClass()->IsChildOf(UMovieSceneSignedObject::StaticClass()))
{
return true;
}
Object = Object->GetOuter();
}
}
return false;
}
void FSequencer::PostUndo(bool bSuccess)
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::Unknown );
SynchronizeSequencerSelectionWithExternalSelection();
OnNodeGroupsCollectionChanged();
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;
if (OwnerMovieScene)
{
OwnerMovieScene->SortMarkedFrames();
}
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
}
void FSequencer::OnNewActorsDropped(const TArray<UObject*>& DroppedObjects, const TArray<AActor*>& DroppedActors)
{
bool bAddSpawnable = FSlateApplication::Get().GetModifierKeys().IsShiftDown();
bool bAddPossessable = FSlateApplication::Get().GetModifierKeys().IsControlDown();
if (bAddSpawnable || bAddPossessable)
{
TArray<AActor*> SpawnedActors;
const FScopedTransaction Transaction(LOCTEXT("UndoAddActors", "Add Actors to Sequencer"));
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = Sequence->GetMovieScene();
if (OwnerMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
Sequence->Modify();
for ( AActor* Actor : DroppedActors )
{
AActor* NewActor = Actor;
bool bCreateAndAttachCamera = false;
if (NewActor->GetClass() == ACameraRig_Rail::StaticClass() ||
NewActor->GetClass() == ACameraRig_Crane::StaticClass())
{
bCreateAndAttachCamera = true;
}
FGuid PossessableGuid = CreateBinding(*NewActor, NewActor->GetActorLabel());
FGuid NewGuid = PossessableGuid;
OnActorAddedToSequencerEvent.Broadcast(NewActor, PossessableGuid);
if (bAddSpawnable)
{
TArray< FMovieSceneSpawnable*> Spawnables = ConvertToSpawnableInternal(PossessableGuid);
if (Spawnables.Num() > 0)
{
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(Spawnables[0]->GetGuid(), ActiveTemplateIDs.Top()))
{
AActor* SpawnedActor = Cast<AActor>(WeakObject.Get());
if (SpawnedActor)
{
SpawnedActors.Add(SpawnedActor);
NewActor = SpawnedActor;
}
}
NewGuid = Spawnables[0]->GetGuid();
}
}
if (bCreateAndAttachCamera)
{
ACameraRig_Rail* RailActor = nullptr;
if (Actor->GetClass() == ACameraRig_Rail::StaticClass())
{
RailActor = Cast<ACameraRig_Rail>(NewActor);
}
// Create a cine camera actor
UWorld* PlaybackContext = Cast<UWorld>(GetPlaybackContext());
ACineCameraActor* NewCamera = PlaybackContext->SpawnActor<ACineCameraActor>();
FGuid NewCameraGuid = CreateBinding(*NewCamera, NewCamera->GetActorLabel());
if (RailActor)
{
NewCamera->SetActorRotation(FRotator(0.f, -90.f, 0.f));
}
OnActorAddedToSequencerEvent.Broadcast(NewCamera, NewCameraGuid);
if (bAddSpawnable)
{
FString NewCameraName = MovieSceneHelpers::MakeUniqueSpawnableName(OwnerMovieScene, FName::NameToDisplayString(ACineCameraActor::StaticClass()->GetFName().ToString(), false));
FMovieSceneSpawnable* Spawnable = ConvertToSpawnableInternal(NewCameraGuid)[0];
Spawnable->SetName(NewCameraName);
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(Spawnable->GetGuid(), ActiveTemplateIDs.Top()))
{
NewCamera = Cast<ACineCameraActor>(WeakObject.Get());
if (NewCamera)
{
break;
}
}
NewCamera->SetActorLabel(NewCameraName, false);
NewCameraGuid = Spawnable->GetGuid();
// Create an attach track
UMovieScene3DAttachTrack* AttachTrack = Cast<UMovieScene3DAttachTrack>(OwnerMovieScene->AddTrack(UMovieScene3DAttachTrack::StaticClass(), NewCameraGuid));
FMovieSceneObjectBindingID AttachBindingID = UE::MovieScene::FRelativeObjectBindingID(NewGuid);
FFrameNumber StartTime = UE::MovieScene::DiscreteInclusiveLower(GetPlaybackRange());
FFrameNumber Duration = UE::MovieScene::DiscreteSize(GetPlaybackRange());
AttachTrack->AddConstraint(StartTime, Duration.Value, NAME_None, NAME_None, AttachBindingID);
}
else
{
// Parent it
NewCamera->AttachToActor(NewActor, FAttachmentTransformRules::KeepRelativeTransform);
}
if (RailActor)
{
// Extend the rail a bit
if (RailActor->GetRailSplineComponent()->GetNumberOfSplinePoints() == 2)
{
FVector SplinePoint1 = RailActor->GetRailSplineComponent()->GetLocationAtSplinePoint(0, ESplineCoordinateSpace::Local);
FVector SplinePoint2 = RailActor->GetRailSplineComponent()->GetLocationAtSplinePoint(1, ESplineCoordinateSpace::Local);
FVector SplineDirection = SplinePoint2 - SplinePoint1;
SplineDirection.Normalize();
float DefaultRailDistance = 650.f;
SplinePoint2 = SplinePoint1 + SplineDirection* DefaultRailDistance;
RailActor->GetRailSplineComponent()->SetLocationAtSplinePoint(1, SplinePoint2, ESplineCoordinateSpace::Local);
RailActor->GetRailSplineComponent()->bSplineHasBeenEdited = true;
}
// Create a track for the CurrentPositionOnRail
FPropertyPath PropertyPath;
PropertyPath.AddProperty(FPropertyInfo(RailActor->GetClass()->FindPropertyByName(TEXT("CurrentPositionOnRail"))));
FKeyPropertyParams KeyPropertyParams(TArrayBuilder<UObject*>().Add(RailActor), PropertyPath, ESequencerKeyMode::ManualKeyForced);
FFrameTime OriginalTime = GetLocalTime().Time;
SetLocalTimeDirectly(UE::MovieScene::DiscreteInclusiveLower(GetPlaybackRange()));
RailActor->CurrentPositionOnRail = 0.f;
KeyProperty(KeyPropertyParams);
SetLocalTimeDirectly(UE::MovieScene::DiscreteExclusiveUpper(GetPlaybackRange())-1);
RailActor->CurrentPositionOnRail = 1.f;
KeyProperty(KeyPropertyParams);
SetLocalTimeDirectly(OriginalTime);
}
NewCameraAdded(NewCamera, NewCameraGuid);
}
}
if (SpawnedActors.Num())
{
const bool bNotifySelectionChanged = true;
const bool bDeselectBSP = true;
const bool bWarnAboutTooManyActors = false;
const bool bSelectEvenIfHidden = false;
GEditor->GetSelectedActors()->Modify();
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
GEditor->SelectNone( bNotifySelectionChanged, bDeselectBSP, bWarnAboutTooManyActors );
for (auto SpawnedActor : SpawnedActors)
{
GEditor->SelectActor( SpawnedActor, true, bNotifySelectionChanged, bSelectEvenIfHidden );
}
GEditor->GetSelectedActors()->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
}
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemsChanged );
SynchronizeSequencerSelectionWithExternalSelection();
}
}
void FSequencer::UpdatePreviewLevelViewportClientFromCameraCut(FLevelEditorViewportClient& InViewportClient, UObject* InCameraObject, const EMovieSceneCameraCutParams& CameraCutParams)
{
AActor* CameraActor = Cast<AActor>(InCameraObject);
AActor* PreviousCameraActor = Cast<AActor>(CameraCutParams.PreviousCameraObject);
const float BlendFactor = FMath::Clamp(CameraCutParams.PreviewBlendFactor, 0.f, 1.f);
const bool bIsBlending = (
(CameraCutParams.bCanBlend) &&
(CameraCutParams.BlendTime > 0.f) &&
(BlendFactor < 1.f - SMALL_NUMBER) &&
(CameraActor != nullptr || PreviousCameraActor != nullptr));
// To preview blending we'll have to offset the viewport camera using the view modifiers API.
ViewModifierInfo.bApplyViewModifier = bIsBlending && !IsInSilentMode();
ViewModifierInfo.BlendFactor = BlendFactor;
ViewModifierInfo.NextCamera = CameraActor;
ViewModifierInfo.PreviousCamera = PreviousCameraActor;
bool bCameraHasBeenCut = CameraCutParams.bJumpCut;
// When possible, let's get values from the camera components instead of the actor itself.
UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(InCameraObject);
UCameraComponent* PreviousCameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(CameraCutParams.PreviousCameraObject);
if (CameraActor)
{
bCameraHasBeenCut = bCameraHasBeenCut || !InViewportClient.IsLockedToActor(CameraActor);
const FVector ViewLocation = CameraComponent ? CameraComponent->GetComponentLocation() : CameraActor->GetActorLocation();
const FRotator ViewRotation = CameraComponent ? CameraComponent->GetComponentRotation() : CameraActor->GetActorRotation();
InViewportClient.SetViewLocation(ViewLocation);
InViewportClient.SetViewRotation(ViewRotation);
}
else
{
if (CameraCutParams.bCanBlend && bHasPreAnimatedInfo)
{
InViewportClient.SetViewLocation(PreAnimatedViewportLocation);
InViewportClient.SetViewRotation(PreAnimatedViewportRotation);
}
}
if (bCameraHasBeenCut)
{
InViewportClient.SetIsCameraCut();
if (UMovieSceneMotionVectorSimulationSystem* MotionVectorSim = RootTemplateInstance.GetEntitySystemLinker()->FindSystem<UMovieSceneMotionVectorSimulationSystem>())
{
MotionVectorSim->SimulateAllTransforms();
}
}
// Set the actor lock.
InViewportClient.SetCinematicActorLock(CameraActor);
InViewportClient.bLockedCameraView = (CameraActor != nullptr);
InViewportClient.RemoveCameraRoll();
// Deal with camera properties.
if (CameraComponent)
{
if (bCameraHasBeenCut)
{
// tell the camera we cut
CameraComponent->NotifyCameraCut();
}
// enforce aspect ratio.
if (CameraComponent->AspectRatio == 0)
{
InViewportClient.AspectRatio = 1.7f;
}
else
{
InViewportClient.AspectRatio = CameraComponent->AspectRatio;
}
// enforce viewport type.
if (CameraComponent->ProjectionMode == ECameraProjectionMode::Type::Perspective)
{
if (InViewportClient.GetViewportType() != LVT_Perspective)
{
InViewportClient.SetViewportType(LVT_Perspective);
}
}
// don't stop the camera from zooming when not playing back
InViewportClient.ViewFOV = CameraComponent->FieldOfView;
// If there are selected actors, invalidate the viewports hit proxies, otherwise they won't be selectable afterwards
if (InViewportClient.Viewport && GEditor->GetSelectedActorCount() > 0)
{
InViewportClient.Viewport->InvalidateHitProxy();
}
}
else
{
InViewportClient.ViewFOV = InViewportClient.FOVAngle;
}
// Update ControllingActorViewInfo, so it is in sync with the updated viewport
InViewportClient.UpdateViewForLockedActor();
}
void FSequencer::SetShowCurveEditor(bool bInShowCurveEditor)
{
SequencerWidget->OnCurveEditorVisibilityChanged(bInShowCurveEditor);
}
bool FSequencer::GetCurveEditorIsVisible() const
{
// Some Sequencer usages don't support the Curve Editor
if (!GetHostCapabilities().bSupportsCurveEditor)
{
return false;
}
// We always want to retrieve this directly from the UI instead of mirroring it to a local bool as there are
// a lot of ways the UI could get out of sync with a local bool (such as previously restored tab layouts)
return GetToolkitHost()->GetTabManager()->FindExistingLiveTab(FTabId(SSequencer::CurveEditorTabName)).IsValid();
}
void FSequencer::SaveCurrentMovieScene()
{
// Capture thumbnail
// Convert UObject* array to FAssetData array
TArray<FAssetData> AssetDataList;
AssetDataList.Add(FAssetData(GetCurrentAsset()));
FViewport* Viewport = GEditor->GetActiveViewport();
// If there's no active viewport, find any other viewport that allows cinematic preview.
if (Viewport == nullptr)
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if ((LevelVC == nullptr) || !LevelVC->AllowsCinematicControl())
{
continue;
}
Viewport = LevelVC->Viewport;
}
}
if (GCurrentLevelEditingViewportClient && Viewport)
{
bool bIsInGameView = GCurrentLevelEditingViewportClient->IsInGameView();
GCurrentLevelEditingViewportClient->SetGameView(true);
//have to re-render the requested viewport
FLevelEditorViewportClient* OldViewportClient = GCurrentLevelEditingViewportClient;
//remove selection box around client during render
GCurrentLevelEditingViewportClient = NULL;
Viewport->Draw();
IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser").Get();
ContentBrowser.CaptureThumbnailFromViewport(Viewport, AssetDataList);
//redraw viewport to have the yellow highlight again
GCurrentLevelEditingViewportClient = OldViewportClient;
GCurrentLevelEditingViewportClient->SetGameView(bIsInGameView);
Viewport->Draw();
}
OnPreSaveEvent.Broadcast(*this);
TArray<UPackage*> PackagesToSave;
TArray<UMovieScene*> MovieScenesToSave;
MovieSceneHelpers::GetDescendantMovieScenes(GetRootMovieSceneSequence(), MovieScenesToSave);
for (auto MovieSceneToSave : MovieScenesToSave)
{
UPackage* MovieScenePackageToSave = MovieSceneToSave->GetOuter()->GetOutermost();
if (MovieScenePackageToSave->IsDirty())
{
PackagesToSave.Add(MovieScenePackageToSave);
}
}
// If there's more than 1 movie scene to save, prompt the user whether to save all dirty movie scenes.
const bool bCheckDirty = PackagesToSave.Num() > 1;
const bool bPromptToSave = PackagesToSave.Num() > 1;
FEditorFileUtils::PromptForCheckoutAndSave( PackagesToSave, bCheckDirty, bPromptToSave );
ForceEvaluate();
OnPostSaveEvent.Broadcast(*this);
}
void FSequencer::SaveCurrentMovieSceneAs()
{
if (!GetHostCapabilities().bSupportsSaveMovieSceneAsset)
{
return;
}
TSharedPtr<IToolkitHost> MyToolkitHost = GetToolkitHost();
check(MyToolkitHost);
TArray<UObject*> AssetsToSave;
AssetsToSave.Add(GetCurrentAsset());
TArray<UObject*> SavedAssets;
FEditorFileUtils::SaveAssetsAs(AssetsToSave, SavedAssets);
if (SavedAssets.Num() == 0)
{
return;
}
if ((SavedAssets[0] != AssetsToSave[0]) && (SavedAssets[0] != nullptr))
{
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
AssetEditorSubsystem->CloseAllEditorsForAsset(AssetsToSave[0]);
AssetEditorSubsystem->OpenEditorForAssets_Advanced(SavedAssets, EToolkitMode::Standalone, MyToolkitHost.ToSharedRef());
}
}
TArray<FGuid> FSequencer::AddActors(const TArray<TWeakObjectPtr<AActor> >& InActors, bool bSelectActors)
{
using namespace UE::Sequencer;
TArray<FGuid> PossessableGuids;
if (GetFocusedMovieSceneSequence()->GetMovieScene()->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return PossessableGuids;
}
const FScopedTransaction Transaction(LOCTEXT("UndoPossessingObject", "Possess Object in Sequencer"));
GetFocusedMovieSceneSequence()->Modify();
bool bPossessableAdded = false;
for (TWeakObjectPtr<AActor> WeakActor : InActors)
{
if (AActor* Actor = WeakActor.Get())
{
FGuid ExistingGuid = FindObjectId(*Actor, ActiveTemplateIDs.Top());
if (!ExistingGuid.IsValid())
{
FGuid PossessableGuid = CreateBinding(*Actor, Actor->GetActorLabel());
PossessableGuids.Add(PossessableGuid);
if (ACameraActor* CameraActor = Cast<ACameraActor>(Actor))
{
NewCameraAdded(CameraActor, PossessableGuid);
}
OnActorAddedToSequencerEvent.Broadcast(Actor, PossessableGuid);
}
bPossessableAdded = true;
}
}
if (bPossessableAdded)
{
// Check if a folder is selected so we can add the actors to the selected folder.
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
if (Selection.GetSelectedOutlinerItems().Num() > 0)
{
for (TWeakPtr<FViewModel> WeakItem : Selection.GetSelectedOutlinerItems())
{
TSharedPtr<FViewModel> CurrentItem = WeakItem.Pin();
if (!CurrentItem)
{
continue;
}
if (TSharedPtr<FFolderModel> Folder = CurrentItem->FindAncestorOfType<FFolderModel>(true))
{
SelectedParentFolders.Add(Folder->GetFolder());
// The first valid folder we find will be used to put the new actors into, so it's the node that we
// want to know the path from.
if (NewNodePath.Len() == 0)
{
// Add an extra delimiter (".") as we know that the new objects will be appended onto the end of this.
NewNodePath = FString::Printf(TEXT("%s."), *IOutlinerExtension::GetPathName(*Folder));
// Make sure the folder is expanded too so that adding objects to hidden folders become visible.
Folder->SetExpansion(true);
}
}
}
}
if (bSelectActors)
{
// Clear our editor selection so we can make the selection our added actors.
// This has to be done after we know if the actor is going to be added to a
// folder, otherwise it causes the folder we wanted to pick to be deselected.
USelection* SelectedActors = GEditor->GetSelectedActors();
SelectedActors->BeginBatchSelectOperation();
SelectedActors->Modify();
GEditor->SelectNone(false, true);
for (TWeakObjectPtr<AActor> WeakActor : InActors)
{
if (AActor* Actor = WeakActor.Get())
{
GEditor->SelectActor(Actor, true, false);
}
}
SelectedActors->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
}
// Add the possessables as children of the first selected folder
if (SelectedParentFolders.Num() > 0)
{
for (const FGuid& Possessable : PossessableGuids)
{
SelectedParentFolders[0]->Modify();
SelectedParentFolders[0]->AddChildObjectBinding(Possessable);
}
}
// Now add them all to the selection set to be selected after a tree rebuild.
if (bSelectActors)
{
for (const FGuid& Possessable : PossessableGuids)
{
FString PossessablePath = NewNodePath + Possessable.ToString();
// Object Bindings use their FGuid as their unique key.
SequencerWidget->AddAdditionalPathToSelectionSet(PossessablePath);
}
}
}
RefreshTree();
SynchronizeSequencerSelectionWithExternalSelection();
return PossessableGuids;
}
void FSequencer::OnSelectedOutlinerNodesChanged()
{
HandleSelectedOutlinerNodesChanged();
}
void FSequencer::HandleSelectedOutlinerNodesChanged()
{
SynchronizeExternalSelectionWithSequencerSelection();
FSequencerEdMode* SequencerEdMode = (FSequencerEdMode*)(GLevelEditorModeTools().GetActiveMode(FSequencerEdMode::EM_SequencerMode));
if (SequencerEdMode != nullptr)
{
AActor* NewlySelectedActor = GEditor->GetSelectedActors()->GetTop<AActor>();
// If we selected an Actor or a node for an Actor that is a potential autokey candidate, clean up any existing mesh trails
if (NewlySelectedActor && !NewlySelectedActor->IsEditorOnly())
{
SequencerEdMode->CleanUpMeshTrails();
}
}
OnSelectionChangedObjectGuidsDelegate.Broadcast(Selection.GetBoundObjectsGuids());
OnSelectionChangedTracksDelegate.Broadcast(Selection.GetSelectedTracks());
TArray<UMovieSceneSection*> SelectedSections;
for (TWeakObjectPtr<UMovieSceneSection> SelectedSectionPtr : Selection.GetSelectedSections())
{
if (SelectedSectionPtr.IsValid())
{
SelectedSections.Add(SelectedSectionPtr.Get());
}
}
OnSelectionChangedSectionsDelegate.Broadcast(SelectedSections);
}
void FSequencer::AddNodeGroupsCollectionChangedDelegate()
{
UMovieSceneSequence* MovieSceneSequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = MovieSceneSequence ? MovieSceneSequence->GetMovieScene() : nullptr;
if (ensure(MovieScene))
{
if (!MovieScene->GetNodeGroups().OnNodeGroupCollectionChanged().IsBoundToObject(this))
{
MovieScene->GetNodeGroups().OnNodeGroupCollectionChanged().AddSP(this, &FSequencer::OnNodeGroupsCollectionChanged);
}
}
}
void FSequencer::RemoveNodeGroupsCollectionChangedDelegate()
{
UMovieSceneSequence* MovieSceneSequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = MovieSceneSequence ? MovieSceneSequence->GetMovieScene() : nullptr;
if (MovieScene)
{
MovieScene->GetNodeGroups().OnNodeGroupCollectionChanged().RemoveAll(this);
}
}
void FSequencer::OnNodeGroupsCollectionChanged()
{
TSharedPtr<SSequencerGroupManager> NodeGroupManager = SequencerWidget->GetNodeGroupsManager();
if (NodeGroupManager)
{
NodeGroupManager->RefreshNodeGroups();
}
NodeTree->NodeGroupsCollectionChanged();
}
void FSequencer::AddSelectedNodesToNewNodeGroup()
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
return;
}
const TSet<TWeakPtr<FViewModel>>& SelectedItems = GetSelection().GetSelectedOutlinerItems();
if (SelectedItems.Num() == 0)
{
return;
}
TSet<FString> NodesToAdd;
for (const TWeakPtr<FViewModel>& WeakItem : SelectedItems)
{
TSharedPtr<FViewModel> Item = WeakItem.Pin();
TSharedPtr<IGroupableExtension> Groupable = Item ? Item->FindAncestorOfType<IGroupableExtension>(true) : nullptr;
if (Groupable)
{
NodesToAdd.Add(Groupable->GetIdentifierForGrouping());
}
}
if (NodesToAdd.Num() == 0)
{
return;
}
TArray<FName> ExistingGroupNames;
for (const UMovieSceneNodeGroup* NodeGroup : MovieScene->GetNodeGroups())
{
ExistingGroupNames.Add(NodeGroup->GetName());
}
const FScopedTransaction Transaction(LOCTEXT("CreateNewGroupTransaction", "Create New Group"));
UMovieSceneNodeGroup* NewNodeGroup = NewObject<UMovieSceneNodeGroup>(&MovieScene->GetNodeGroups(), NAME_None, RF_Transactional);
NewNodeGroup->SetName(FSequencerUtilities::GetUniqueName(FName("Group"), ExistingGroupNames));
for (const FString& NodeToAdd : NodesToAdd)
{
NewNodeGroup->AddNode(NodeToAdd);
}
MovieScene->GetNodeGroups().AddNodeGroup(NewNodeGroup);
SequencerWidget->OpenNodeGroupsManager();
SequencerWidget->GetNodeGroupsManager()->RequestRenameNodeGroup(NewNodeGroup);
}
void FSequencer::AddSelectedNodesToExistingNodeGroup(UMovieSceneNodeGroup* NodeGroup)
{
AddNodesToExistingNodeGroup(GetSelection().GetSelectedOutlinerItems().Array(), NodeGroup);
}
void FSequencer::AddNodesToExistingNodeGroup(const TArray<TWeakPtr<UE::Sequencer::FViewModel>>& InItems, UMovieSceneNodeGroup* InNodeGroup)
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
return;
}
if (!MovieScene->GetNodeGroups().Contains(InNodeGroup))
{
return;
}
TSet<FString> NodesToAdd;
for (const TWeakPtr<FViewModel>& WeakItem : InItems)
{
TSharedPtr<FViewModel> Item = WeakItem.Pin();
TSharedPtr<IGroupableExtension> Groupable = Item ? Item->FindAncestorOfType<IGroupableExtension>(true) : nullptr;
if (Groupable)
{
NodesToAdd.Add(Groupable->GetIdentifierForGrouping());
}
}
if (NodesToAdd.Num() == 0)
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("AddNodesToGroupTransaction", "Add Nodes to Group"));
for (const FString& NodeToAdd : NodesToAdd)
{
if (!InNodeGroup->ContainsNode(NodeToAdd))
{
InNodeGroup->AddNode(NodeToAdd);
}
}
}
void FSequencer::ClearFilters()
{
SequencerWidget->SetSearchText(FText::GetEmpty());
GetNodeTree()->RemoveAllFilters();
GetSequencerSettings()->SetShowSelectedNodesOnly(false);
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
UMovieScene* FocusedMovieScene = nullptr;
if (IsValid(FocusedMovieSequence))
{
FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (IsValid(FocusedMovieScene))
{
for (UMovieSceneNodeGroup* NodeGroup : FocusedMovieScene->GetNodeGroups())
{
NodeGroup->SetEnableFilter(false);
}
}
}
}
void FSequencer::SynchronizeExternalSelectionWithSequencerSelection()
{
using namespace UE::Sequencer;
if ( bUpdatingSequencerSelection || !IsLevelEditorSequencer() )
{
return;
}
TGuardValue<bool> Guard(bUpdatingExternalSelection, true);
TSet<AActor*> SelectedSequencerActors;
TSet<UActorComponent*> SelectedSequencerComponents;
TSet<TWeakPtr<FViewModel>> Items = Selection.GetNodesWithSelectedKeysOrSections();
Items.Append(Selection.GetSelectedOutlinerItems());
const FName ControlRigEditModeModeName("EditMode.ControlRig");
const bool bHACK_ControlRigEditMode = GLevelEditorModeTools().GetActiveMode(ControlRigEditModeModeName) != nullptr;
for ( TWeakPtr<FViewModel> WeakItem : Items)
{
TSharedPtr<FViewModel> SelectedItem = WeakItem.Pin();
if (!SelectedItem)
{
continue;
}
//HACK for DHI, if we have an active control rig then one is selected so don't find a parent actor or compomonent to select
//but if we do select the actor/compoent directly we still select it.
TViewModelPtr<IObjectBindingExtension> ObjectBinding = bHACK_ControlRigEditMode
? FViewModelPtr(SelectedItem)
: SelectedItem->FindAncestorOfType(IObjectBindingExtension::ID, true);
// If the closest node is an object node, try to get the actor/component nodes from it.
if (ObjectBinding)
{
for (auto RuntimeObject : FindBoundObjects(ObjectBinding->GetObjectGuid(), ActiveTemplateIDs.Top()) )
{
AActor* Actor = Cast<AActor>(RuntimeObject.Get());
if ( Actor != nullptr )
{
ULevel* ActorLevel = Actor->GetLevel();
if (!FLevelUtils::IsLevelLocked(ActorLevel))
{
SelectedSequencerActors.Add(Actor);
}
}
UActorComponent* ActorComponent = Cast<UActorComponent>(RuntimeObject.Get());
if ( ActorComponent != nullptr )
{
if (!FLevelUtils::IsLevelLocked(ActorComponent->GetOwner()->GetLevel()))
{
SelectedSequencerComponents.Add(ActorComponent);
Actor = ActorComponent->GetOwner();
if (Actor != nullptr)
{
SelectedSequencerActors.Add(Actor);
}
}
}
}
}
}
const bool bNotifySelectionChanged = false;
const bool bDeselectBSP = true;
const bool bWarnAboutTooManyActors = false;
const bool bSelectEvenIfHidden = true;
if (SelectedSequencerComponents.Num() + SelectedSequencerActors.Num() == 0)
{
if (GEditor->GetSelectedActorCount())
{
const FScopedTransaction Transaction(NSLOCTEXT("Sequencer", "UpdatingActorComponentSelectionNone", "Select None"), !GIsTransacting);
GEditor->SelectNone( bNotifySelectionChanged, bDeselectBSP, bWarnAboutTooManyActors );
GEditor->NoteSelectionChange();
}
return;
}
// We need to check if the selection has changed. Rebuilding the selection set if it hasn't changed can cause unwanted side effects.
bool bIsSelectionChanged = false;
// Check if any actors have been added to the selection
for (AActor* SelectedSequencerActor : SelectedSequencerActors)
{
if (!GEditor->GetSelectedActors()->IsSelected(SelectedSequencerActor))
{
bIsSelectionChanged = true;
break;
}
}
// Check if any actors have been removed from the selection
if (!bIsSelectionChanged)
{
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
if (AActor* CurrentlySelectedActor = Cast<AActor>(*It))
{
if (!SelectedSequencerActors.Contains(CurrentlySelectedActor))
{
bIsSelectionChanged = true;
break;
}
}
}
}
// Check if any components have been added to the selection
if (!bIsSelectionChanged)
{
for (UActorComponent* SelectedSequencerComponent : SelectedSequencerComponents)
{
if (!GEditor->GetSelectedComponents()->IsSelected(SelectedSequencerComponent))
{
bIsSelectionChanged = true;
break;
}
}
}
// Check if any components have been removed from the selection
if (!bIsSelectionChanged)
{
for (FSelectionIterator It(GEditor->GetSelectedComponentIterator()); It; ++It)
{
if (UActorComponent* CurrentlySelectedComponent = Cast<UActorComponent>(*It))
{
if (!SelectedSequencerComponents.Contains(CurrentlySelectedComponent))
{
bIsSelectionChanged = true;
break;
}
}
}
}
if (!bIsSelectionChanged)
{
return;
}
const FScopedTransaction Transaction(NSLOCTEXT("Sequencer", "UpdatingActorComponentSelection", "Select Actors/Components"), !GIsTransacting);
GEditor->GetSelectedActors()->Modify();
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
GEditor->SelectNone( bNotifySelectionChanged, bDeselectBSP, bWarnAboutTooManyActors );
for (AActor* SelectedSequencerActor : SelectedSequencerActors)
{
GEditor->SelectActor(SelectedSequencerActor, true, bNotifySelectionChanged, bSelectEvenIfHidden);
}
GEditor->GetSelectedActors()->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
if (SelectedSequencerComponents.Num())
{
GEditor->GetSelectedComponents()->Modify();
GEditor->GetSelectedComponents()->BeginBatchSelectOperation();
for (UActorComponent* SelectedSequencerComponent : SelectedSequencerComponents)
{
GEditor->SelectComponent(SelectedSequencerComponent, true, bNotifySelectionChanged, bSelectEvenIfHidden);
}
GEditor->GetSelectedComponents()->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
}
}
void FSequencer::SynchronizeSequencerSelectionWithExternalSelection()
{
using namespace UE::Sequencer;
if ( bUpdatingExternalSelection )
{
return;
}
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if( !IsLevelEditorSequencer() )
{
// Only level sequences have a full update here, but we still want filters to update for UMG animations
NodeTree->RequestFilterUpdate();
return;
}
if (!Sequence || !Sequence->GetMovieScene())
{
return;
}
TGuardValue<bool> Guard(bUpdatingSequencerSelection, true);
// If all nodes are already selected, do nothing. This ensures that when an undo event happens,
// nodes are not cleared and reselected, which can cause issues with the curve editor auto-fitting
// based on selection.
bool bAllAlreadySelected = true;
USelection* ActorSelection = GEditor->GetSelectedActors();
// Get the selected sequencer keys for viewport interaction
TArray<ASequencerKeyActor*> SelectedSequencerKeyActors;
ActorSelection->GetSelectedObjects<ASequencerKeyActor>(SelectedSequencerKeyActors);
FObjectBindingModelStorageExtension* ObjectModelStorage = ViewModel->GetRootModel()->CastDynamic<FObjectBindingModelStorageExtension>();
check(ObjectModelStorage);
TSet<TSharedPtr<FObjectBindingModel>> NodesToSelect;
for (const FMovieSceneBinding& Binding : Sequence->GetMovieScene()->GetBindings())
{
TSharedPtr<FObjectBindingModel> ObjectModel = ObjectModelStorage->FindModelForObjectBinding(Binding.GetObjectGuid());
if (!ObjectModel)
{
continue;
}
for ( TWeakObjectPtr<> WeakObject : FindBoundObjects(Binding.GetObjectGuid(), ActiveTemplateIDs.Top()) )
{
UObject* RuntimeObject = WeakObject.Get();
if (RuntimeObject == nullptr)
{
continue;
}
for (ASequencerKeyActor* KeyActor : SelectedSequencerKeyActors)
{
if (KeyActor->IsEditorOnly())
{
AActor* TrailActor = KeyActor->GetAssociatedActor();
if (TrailActor != nullptr && RuntimeObject == TrailActor)
{
NodesToSelect.Add(ObjectModel);
bAllAlreadySelected = false;
break;
}
}
}
AActor* Actor = Cast<AActor>(RuntimeObject);
const bool bActorSelected = Actor ? ActorSelection->IsSelected(Actor) : false;
UActorComponent* ActorComponent = Cast<UActorComponent>(RuntimeObject);
const bool bComponentSelected = ActorComponent ? GEditor->GetSelectedComponents()->IsSelected(ActorComponent) : false;
if (bActorSelected || bComponentSelected)
{
NodesToSelect.Add( ObjectModel );
if (bAllAlreadySelected && !Selection.IsSelected(ObjectModel))
{
bool bAnyChildrenSelected = false;
TArray<TSharedPtr<FViewModel>> OutlinerChildren;
ObjectModel->GetDescendantsOfType(IOutlinerExtension::ID, OutlinerChildren);
for (TSharedPtr<FViewModel> Child : OutlinerChildren)
{
if (Selection.IsSelected(Child) || Selection.NodeHasSelectedKeysOrSections(Child))
{
bAnyChildrenSelected = true;
break;
}
}
if (!bAnyChildrenSelected)
{
bAllAlreadySelected = false;
}
}
}
else if (Selection.IsSelected(ObjectModel))
{
bAllAlreadySelected = false;
}
}
}
//Only test if none are selected if we are not transacting, otherwise it will clear out control rig's incorrectly.
if (!bAllAlreadySelected || (!GIsTransacting && (NodesToSelect.Num() == 0 && Selection.GetSelectedOutlinerItems().Num())))
{
Selection.SuspendBroadcast();
Selection.EmptySelectedOutlinerNodes();
for (TSharedPtr<FViewModel> NodeToSelect : NodesToSelect)
{
Selection.AddToSelection( NodeToSelect );
}
TSharedPtr<SOutlinerView> TreeView = SequencerWidget->GetTreeView();
const TSet<TWeakPtr<FViewModel>>& OutlinerSelection = GetSelection().GetSelectedOutlinerItems();
if (OutlinerSelection.Num() == 1)
{
for (TWeakPtr<FViewModel> WeakNode : OutlinerSelection)
{
if (TSharedPtr<FViewModel> Node = WeakNode.Pin())
{
TSharedPtr<FViewModel> Parent = Node->GetParent();
while (Parent.IsValid())
{
TreeView->SetItemExpansion(Parent->AsShared(), true);
Parent = Parent->GetParent();
}
TreeView->RequestScrollIntoView(Node);
break;
}
}
}
Selection.ResumeBroadcast();
Selection.GetOnOutlinerNodeSelectionChanged().Broadcast();
}
}
void FSequencer::SelectNodesByPath(const TSet<FString>& NodePaths)
{
using namespace UE::Sequencer;
if (bUpdatingExternalSelection)
{
return;
}
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if (!Sequence->GetMovieScene())
{
return;
}
// If all nodes are already selected, do nothing. This ensures that when an undo event happens,
// nodes are not cleared and reselected, which can cause issues with the curve editor auto-fitting
// based on selection.
bool bAllAlreadySelected = true;
const TSet<TWeakPtr<FViewModel>>& CurrentSelection = GetSelection().GetSelectedOutlinerItems();
TArray<TSharedPtr<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
TSet<TSharedPtr<FViewModel>> NodesToSelect;
for (TSharedPtr<FViewModel> DisplayNode : AllNodes)
{
if (NodePaths.Contains(IOutlinerExtension::GetPathName(DisplayNode)))
{
NodesToSelect.Add(DisplayNode);
if (bAllAlreadySelected && !CurrentSelection.Contains(DisplayNode))
{
bAllAlreadySelected = false;
}
}
}
if (!bAllAlreadySelected || (NodesToSelect.Num() != CurrentSelection.Num()))
{
Selection.SuspendBroadcast();
Selection.EmptySelectedOutlinerNodes();
for (TSharedPtr<FViewModel> NodeToSelect : NodesToSelect)
{
Selection.AddToSelection( NodeToSelect );
}
TSharedPtr<SOutlinerView> TreeView = SequencerWidget->GetTreeView();
const TSet<TWeakPtr<FViewModel>>& OutlinerSelection = GetSelection().GetSelectedOutlinerItems();
for (const TWeakPtr<FViewModel>& WeakNode : OutlinerSelection)
{
if (TSharedPtr<FViewModel> Node = WeakNode.Pin())
{
TSharedPtr<FViewModel> Parent = Node->GetParent();
while (Parent.IsValid())
{
TreeView->SetItemExpansion(Parent->AsShared(), true);
Parent = Parent->GetParent();
}
TreeView->RequestScrollIntoView(Node);
break;
}
}
Selection.ResumeBroadcast();
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
bool FSequencer::IsBindingVisible(const FMovieSceneBinding& InBinding)
{
if (Settings->GetShowSelectedNodesOnly() && OnGetIsBindingVisible().IsBound())
{
return OnGetIsBindingVisible().Execute(InBinding);
}
return true;
}
bool FSequencer::IsTrackVisible(const UMovieSceneTrack* InTrack)
{
if (Settings->GetShowSelectedNodesOnly() && OnGetIsTrackVisible().IsBound())
{
return OnGetIsTrackVisible().Execute(InTrack);
}
return true;
}
void FSequencer::OnNodePathChanged(const FString& OldPath, const FString& NewPath)
{
if (!OldPath.Equals(NewPath))
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
MovieScene->GetNodeGroups().UpdateNodePath(OldPath, NewPath);
// If the node is in the solo list, replace it with it's new path
if (MovieScene->GetSoloNodes().Remove(OldPath))
{
MovieScene->GetSoloNodes().Add(NewPath);
}
// If the node is in the mute list, replace it with it's new path
if (MovieScene->GetMuteNodes().Remove(OldPath))
{
MovieScene->GetMuteNodes().Add(NewPath);
}
// Find any solo/muted nodes with a path that is a child of the renamed node, and rename their paths as well
FString PathPrefix = OldPath + '.';
TArray<FString> PathsToRename;
for (const FString& NodePath : MovieScene->GetSoloNodes())
{
if (NodePath.StartsWith(PathPrefix) && NodePath != NewPath)
{
PathsToRename.Add(NodePath);
}
}
for (const FString& NodePath : PathsToRename)
{
FString NewNodePath = NodePath;
if (NewNodePath.RemoveFromStart(PathPrefix))
{
NewNodePath = NewPath + '.' + NewNodePath;
if (NodeTree->GetNodeAtPath(NewNodePath))
{
MovieScene->GetSoloNodes().Remove(NodePath);
MovieScene->GetSoloNodes().Add(NewNodePath);
}
}
}
PathsToRename.Empty();
for (const FString& NodePath : MovieScene->GetMuteNodes())
{
if (NodePath.StartsWith(PathPrefix) && NodePath != NewPath)
{
PathsToRename.Add(NodePath);
}
}
for (const FString& NodePath : PathsToRename)
{
FString NewNodePath = NodePath;
if (NewNodePath.RemoveFromStart(PathPrefix))
{
NewNodePath = NewPath + '.' + NewNodePath;
if (NodeTree->GetNodeAtPath(NewNodePath))
{
MovieScene->GetMuteNodes().Remove(NodePath);
MovieScene->GetMuteNodes().Add(NewNodePath);
}
}
}
}
}
void FSequencer::OnSelectedNodesOnlyChanged()
{
RefreshTree();
SynchronizeSequencerSelectionWithExternalSelection();
}
void FSequencer::ZoomToFit()
{
FFrameRate TickResolution = GetFocusedTickResolution();
TRange<FFrameNumber> BoundsHull = TRange<FFrameNumber>::All();
for ( const FSequencerSelectedKey& Key : Selection.GetSelectedKeys().Array() )
{
if (Key.IsValid())
{
FFrameNumber KeyTime = Key.WeakChannel.Pin()->GetKeyArea()->GetKeyTime(Key.KeyHandle);
if (!BoundsHull.HasLowerBound() || BoundsHull.GetLowerBoundValue() > KeyTime)
{
BoundsHull.SetLowerBound(TRange<FFrameNumber>::BoundsType::Inclusive(KeyTime));
}
if (!BoundsHull.HasUpperBound() || BoundsHull.GetUpperBoundValue() < KeyTime)
{
BoundsHull.SetUpperBound(TRange<FFrameNumber>::BoundsType::Inclusive(KeyTime));
}
}
}
for (TWeakObjectPtr<UMovieSceneSection> SelectedSection : Selection.GetSelectedSections())
{
if (SelectedSection->GetRange().HasUpperBound() && SelectedSection->GetRange().HasLowerBound())
{
if (BoundsHull == TRange<FFrameNumber>::All())
{
BoundsHull = SelectedSection->GetRange();
}
else
{
BoundsHull = TRange<FFrameNumber>::Hull(SelectedSection->GetRange(), BoundsHull);
}
}
}
if (BoundsHull.IsEmpty() || BoundsHull == TRange<FFrameNumber>::All())
{
BoundsHull = GetTimeBounds();
}
if (!BoundsHull.IsEmpty() && !BoundsHull.IsDegenerate())
{
const double Tolerance = KINDA_SMALL_NUMBER;
// Zoom back to last view range if already expanded
if (!ViewRangeBeforeZoom.IsEmpty() &&
FMath::IsNearlyEqual(BoundsHull.GetLowerBoundValue() / TickResolution, GetViewRange().GetLowerBoundValue(), Tolerance) &&
FMath::IsNearlyEqual(BoundsHull.GetUpperBoundValue() / TickResolution, GetViewRange().GetUpperBoundValue(), Tolerance))
{
SetViewRange(ViewRangeBeforeZoom, EViewRangeInterpolation::Animated);
}
else
{
ViewRangeBeforeZoom = GetViewRange();
TRange<double> BoundsHullSeconds = BoundsHull / TickResolution;
const double OutputViewSize = BoundsHullSeconds.Size<double>();
const double OutputChange = OutputViewSize * 0.1f;
if (OutputChange > 0)
{
BoundsHullSeconds = UE::MovieScene::ExpandRange(BoundsHullSeconds, OutputChange);
SetViewRange(BoundsHullSeconds, EViewRangeInterpolation::Animated);
}
}
}
}
bool FSequencer::CanKeyProperty(FCanKeyPropertyParams CanKeyPropertyParams) const
{
return ObjectChangeListener->CanKeyProperty(CanKeyPropertyParams);
}
void FSequencer::KeyProperty(FKeyPropertyParams KeyPropertyParams)
{
ObjectChangeListener->KeyProperty(KeyPropertyParams);
}
FSequencerSelection& FSequencer::GetSelection()
{
return Selection;
}
FSequencerSelectionPreview& FSequencer::GetSelectionPreview()
{
return SelectionPreview;
}
void FSequencer::SuspendSelectionBroadcast()
{
Selection.SuspendBroadcast();
}
void FSequencer::ResumeSelectionBroadcast()
{
Selection.ResumeBroadcast();
}
void FSequencer::GetSelectedTracks(TArray<UMovieSceneTrack*>& OutSelectedTracks)
{
OutSelectedTracks.Append(Selection.GetSelectedTracks());
}
void FSequencer::GetSelectedSections(TArray<UMovieSceneSection*>& OutSelectedSections)
{
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : Selection.GetSelectedSections())
{
if (UMovieSceneSection* Section = WeakSection.Get())
{
OutSelectedSections.Add(Section);
}
}
}
void FSequencer::GetSelectedFolders(TArray<UMovieSceneFolder*>& OutSelectedFolders)
{
FString OutNewNodePath;
CalculateSelectedFolderAndPath(OutSelectedFolders, OutNewNodePath);
}
void FSequencer::GetSelectedObjects(TArray<FGuid>& Objects)
{
Objects = GetSelection().GetBoundObjectsGuids();
}
void FSequencer::GetSelectedKeyAreas(TArray<const IKeyArea*>& OutSelectedKeyAreas, bool bIncludeSelectedKeys)
{
using namespace UE::Sequencer;
TArray<TSharedRef<FViewModel>> NodesToKey;
{
TSet<TSharedRef<FViewModel>> ChildNodes;
for (const TWeakPtr<FViewModel>& WeakNode : Selection.GetSelectedOutlinerItems())
{
TSharedPtr<FViewModel> Node = WeakNode.Pin();
if (!Node)
{
continue;
}
NodesToKey.Add(Node.ToSharedRef());
// No need to gather key areas from binding/tracks because they have no key areas
if (Node->IsA<IObjectBindingExtension>() || Node->IsA<ITrackExtension>() || Node->IsA<FFolderModel>())
{
continue;
}
ChildNodes.Reset();
SequencerHelpers::GetDescendantNodes(Node.ToSharedRef(), ChildNodes);
for (TSharedRef<FViewModel> ChildNode : ChildNodes)
{
NodesToKey.Remove(ChildNode);
}
}
}
TSet<TSharedPtr<IKeyArea>> KeyAreas;
TSet<UMovieSceneSection*> ModifiedSections;
for (TSharedRef<FViewModel> Node : NodesToKey)
{
//if object or track selected we don't want all of the children only if spefically selected.
if (!Node->IsA<ITrackExtension>() && !Node->IsA<IObjectBindingExtension>() && !Node->IsA<FFolderModel>())
{
SequencerHelpers::GetAllKeyAreas(Node, KeyAreas);
}
}
if (bIncludeSelectedKeys)
{
for (FSequencerSelectedKey Key : Selection.GetSelectedKeys())
{
KeyAreas.Add(Key.WeakChannel.Pin()->GetKeyArea());
}
}
for (TSharedPtr<IKeyArea> KeyArea : KeyAreas)
{
const IKeyArea* KeyAreaPtr = KeyArea.Get();
OutSelectedKeyAreas.Add(KeyAreaPtr);
}
}
void FSequencer::SelectByNthCategoryNode(UMovieSceneSection* Section, int Index, bool bSelect)
{
using namespace UE::Sequencer;
TSet<TSharedRef<FViewModel>> Nodes;
TArray<TSharedRef<FViewModel>> NodesToSelect;
TSharedPtr<FSectionModel> SectionHandle = NodeTree->GetSectionModel(Section);
int32 Count = 0;
if (SectionHandle)
{
TSharedPtr<FViewModel> TrackNode = SectionHandle->GetParentTrackModel();
for (TSharedPtr<FViewModel> Node : TrackNode->GetChildren())
{
if (Node->IsA<FCategoryGroupModel>() && Count++ == Index)
{
bool bAlreadySelected = false;
if (bSelect == true)
{
bAlreadySelected = Selection.GetSelectedOutlinerItems().Contains(Node);
}
if (bAlreadySelected == false)
{
NodesToSelect.Add(Node.ToSharedRef());
if (bSelect == false) //make sure all children not selected
{
for (TSharedPtr<FViewModel> ChildNode : Node->GetChildren())
{
NodesToSelect.Add(ChildNode.ToSharedRef());
}
}
}
}
}
}
if (bSelect)
{
if (Settings->GetAutoExpandTreeView())
{
for (const TSharedRef<FViewModel>& DisplayNode : NodesToSelect)
{
if (DisplayNode->GetParent().IsValid() && DisplayNode->GetParent()->IsA<ITrackExtension>())
{
if (IOutlinerExtension* ParentOutlinerItem = DisplayNode->GetParent()->CastThis<IOutlinerExtension>())
{
if (!ParentOutlinerItem->IsExpanded())
{
ParentOutlinerItem->SetExpansion(true);
}
}
break;
}
}
}
if (NodesToSelect.Num() > 0)
{
SequencerWidget->GetTreeView()->RequestScrollIntoView(NodesToSelect[0]);
Selection.AddToSelection(NodesToSelect);
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
else if (NodesToSelect.Num() > 0)
{
for (const TSharedRef<FViewModel>& DisplayNode : NodesToSelect)
{
Selection.RemoveFromSelection(DisplayNode);
}
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
void FSequencer::SelectByChannels(UMovieSceneSection* Section, TArrayView<const FMovieSceneChannelHandle> InChannels, bool bSelectParentInstead, bool bSelect)
{
using namespace UE::Sequencer;
TSet<TSharedRef<FViewModel>> Nodes;
TArray<TSharedRef<FViewModel>> NodesToSelect;
TSharedPtr<FSectionModel> SectionHandle = NodeTree->GetSectionModel(Section);
if (SectionHandle)
{
TSharedPtr<FViewModel> TrackNode = SectionHandle->GetParentTrackModel();
for (TSharedPtr<FChannelGroupModel> KeyAreaNode : TrackNode->GetDescendantsOfType<FChannelGroupModel>())
{
for (TSharedPtr<IKeyArea> KeyArea : KeyAreaNode->GetAllKeyAreas())
{
FMovieSceneChannelHandle ThisChannel = KeyArea->GetChannel();
if (Algo::Find(InChannels, ThisChannel) != nullptr)
{
if (bSelectParentInstead || bSelect == false)
{
if (bSelect)
{
NodesToSelect.Add(KeyAreaNode->GetParent().ToSharedRef());
}
else
{
Nodes.Add(KeyAreaNode->GetParent().ToSharedRef());
}
}
if (!bSelectParentInstead || bSelect == false)
{
if (bSelect)
{
NodesToSelect.Add(KeyAreaNode.ToSharedRef());
}
else
{
Nodes.Add(KeyAreaNode.ToSharedRef());
}
}
}
}
}
}
if (bSelect)
{
if (NodesToSelect.Num() > 0)
{
//todo hide behind preference
SequencerWidget->GetTreeView()->RequestScrollIntoView(NodesToSelect[0]);
Selection.AddToSelection(NodesToSelect);
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
else if (Nodes.Num() > 0)
{
for (const TSharedRef<FViewModel>& DisplayNode : Nodes)
{
Selection.RemoveFromSelection(DisplayNode);
}
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
void FSequencer::SelectByChannels(UMovieSceneSection* Section, const TArray<FName>& InChannelNames, bool bSelectParentInstead, bool bSelect)
{
using namespace UE::Sequencer;
TSet<TSharedRef<FViewModel>> Nodes;
TArray<TSharedRef<FViewModel>> NodesToSelect;
TSharedPtr<FSectionModel> SectionHandle = NodeTree->GetSectionModel(Section);
if (SectionHandle)
{
TSharedPtr<FViewModel> TrackNode = SectionHandle->GetParentTrackModel();
for (TSharedPtr<FChannelGroupModel> KeyAreaNode : TrackNode->GetDescendantsOfType<FChannelGroupModel>())
{
if (KeyAreaNode->GetParent().IsValid() && InChannelNames.Contains(*KeyAreaNode->GetParent()->CastThis<IOutlinerExtension>()->GetLabel().ToString()))
{
Nodes.Add(KeyAreaNode->GetParent().ToSharedRef());
}
for (TSharedPtr<IKeyArea> KeyArea : KeyAreaNode->GetAllKeyAreas())
{
FMovieSceneChannelHandle ThisChannel = KeyArea->GetChannel();
const FMovieSceneChannelMetaData* MetaData = ThisChannel.GetMetaData();
if (MetaData && InChannelNames.Contains(MetaData->Name))
{
if (bSelectParentInstead || bSelect == false)
{
Nodes.Add(KeyAreaNode->GetParent().ToSharedRef());
}
if (!bSelectParentInstead || bSelect == false)
{
Nodes.Add(KeyAreaNode.ToSharedRef());
}
}
}
}
}
if (bSelect)
{
for (const TSharedRef<FViewModel>& DisplayNode : Nodes)
{
if (Settings->GetAutoExpandTreeView())
{
if (DisplayNode->GetParent().IsValid() && DisplayNode->GetParent()->IsA<ITrackExtension>() && !DisplayNode->GetParent()->CastThisChecked<IOutlinerExtension>()->IsExpanded())
{
DisplayNode->GetParent()->CastThisChecked<IOutlinerExtension>()->SetExpansion(true);
}
}
NodesToSelect.Add(DisplayNode);
}
if (NodesToSelect.Num() > 0)
{
SequencerWidget->GetTreeView()->RequestScrollIntoView(NodesToSelect[0]);
Selection.AddToSelection(NodesToSelect);
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
else if (Nodes.Num() > 0)
{
for (const TSharedRef<FViewModel>& DisplayNode : Nodes)
{
Selection.RemoveFromSelection(DisplayNode);
}
Selection.RequestOutlinerNodeSelectionChangedBroadcast();
}
}
void FSequencer::SelectObject(FGuid ObjectBinding)
{
using namespace UE::Sequencer;
FObjectBindingModelStorageExtension* ObjectStorage = ViewModel->GetRootModel()->CastDynamic<FObjectBindingModelStorageExtension>();
check(ObjectStorage);
if (TSharedPtr<FObjectBindingModel> Model = ObjectStorage->FindModelForObjectBinding(ObjectBinding))
{
Selection.AddToOutlinerSelection(Model);
}
}
void FSequencer::SelectTrack(UMovieSceneTrack* Track)
{
using namespace UE::Sequencer;
TArray<TSharedPtr<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
for (TSharedPtr<FViewModel> Node : AllNodes)
{
if (ITrackExtension* TrackNode = Node->CastThis<ITrackExtension>())
{
UMovieSceneTrack* TrackForNode = TrackNode->GetTrack();
if (TrackForNode == Track)
{
Selection.AddToOutlinerSelection(Node);
break;
}
}
}
}
void FSequencer::SelectSection(UMovieSceneSection* Section)
{
using namespace UE::Sequencer;
FSectionModelStorageExtension* SectionModelStorage = ViewModel->GetRootModel()->CastDynamic<FSectionModelStorageExtension>();
check(SectionModelStorage);
TSharedPtr<UE::Sequencer::FSectionModel> SectionModel = SectionModelStorage->FindModelForSection(Section);
if (SectionModel)
{
Selection.AddToSelection(SectionModel);
}
}
void FSequencer::SelectKey(UMovieSceneSection* InSection, TSharedPtr<UE::Sequencer::FChannelModel> InChannel, FKeyHandle KeyHandle, bool bToggle)
{
FSequencerSelectedKey SelectedKey(*InSection, InChannel, KeyHandle);
if (bToggle && Selection.IsSelected(SelectedKey))
{
Selection.RemoveFromSelection(SelectedKey);
}
else
{
Selection.AddToSelection(SelectedKey);
}
}
void FSequencer::SelectByPropertyPaths(const TArray<FString>& InPropertyPaths)
{
using namespace UE::Sequencer;
TArray<TSharedRef<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
TArray<TSharedRef<FViewModel>> NodesToSelect;
for (const TSharedRef<FViewModel>& Node : AllNodes)
{
if (ITrackExtension* TrackNode = Node->CastThis<ITrackExtension>())
{
if (UMovieScenePropertyTrack* PropertyTrack = Cast<UMovieScenePropertyTrack>(TrackNode->GetTrack()))
{
FString Path = PropertyTrack->GetPropertyPath().ToString();
for (const FString& PropertyPath : InPropertyPaths)
{
if (Path == PropertyPath)
{
NodesToSelect.Add(Node);
break;
}
}
}
}
}
Selection.SuspendBroadcast();
Selection.Empty();
Selection.ResumeBroadcast();
if (NodesToSelect.Num())
{
Selection.AddToSelection(NodesToSelect);
}
}
void FSequencer::SelectFolder(UMovieSceneFolder* Folder)
{
using namespace UE::Sequencer;
TArray<TSharedRef<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
for (TSharedRef<FViewModel> Node : AllNodes)
{
if (FFolderModel* FolderNode = Node->CastThis<FFolderModel>())
{
UMovieSceneFolder* FolderForNode = FolderNode->GetFolder();
if (FolderForNode == Folder)
{
Selection.AddToSelection(Node);
break;
}
}
}
}
void FSequencer::EmptySelection()
{
Selection.Empty();
}
void FSequencer::ThrobKeySelection()
{
using namespace UE::Sequencer;
SSequencerSection::ThrobKeySelection();
}
void FSequencer::ThrobSectionSelection()
{
using namespace UE::Sequencer;
// Scrub to the beginning of newly created sections if they're out of view
TOptional<FFrameNumber> ScrubFrame;
for (TWeakObjectPtr<UMovieSceneSection> SelectedSectionPtr : Selection.GetSelectedSections())
{
if (SelectedSectionPtr.IsValid() && SelectedSectionPtr->HasStartFrame())
{
if (!ScrubFrame.IsSet() || (ScrubFrame.GetValue() > SelectedSectionPtr->GetInclusiveStartFrame()))
{
ScrubFrame = SelectedSectionPtr->GetInclusiveStartFrame();
}
}
}
if (ScrubFrame.IsSet())
{
float ScrubTime = GetFocusedDisplayRate().AsSeconds(FFrameRate::TransformTime(ScrubFrame.GetValue(), GetFocusedTickResolution(), GetFocusedDisplayRate()));
TRange<double> NewViewRange = GetViewRange();
if (!NewViewRange.Contains(ScrubTime))
{
double MidRange = (NewViewRange.GetUpperBoundValue() - NewViewRange.GetLowerBoundValue()) / 2.0 + NewViewRange.GetLowerBoundValue();
NewViewRange.SetLowerBoundValue(NewViewRange.GetLowerBoundValue() - (MidRange - ScrubTime));
NewViewRange.SetUpperBoundValue(NewViewRange.GetUpperBoundValue() - (MidRange - ScrubTime));
SetViewRange(NewViewRange, EViewRangeInterpolation::Animated);
}
}
SSequencerSection::ThrobSectionSelection();
}
float FSequencer::GetOverlayFadeCurve() const
{
return OverlayCurve.GetLerp();
}
void FSequencer::DeleteSelectedItems()
{
if (Selection.GetSelectedKeys().Num())
{
FScopedTransaction DeleteKeysTransaction( NSLOCTEXT("Sequencer", "DeleteKeys_Transaction", "Delete Keys") );
DeleteSelectedKeys();
}
else if (Selection.GetSelectedSections().Num())
{
FScopedTransaction DeleteSectionsTransaction( NSLOCTEXT("Sequencer", "DeleteSections_Transaction", "Delete Sections") );
DeleteSections(Selection.GetSelectedSections());
}
else if (Selection.GetSelectedOutlinerItems().Num())
{
DeleteSelectedNodes(false);
}
}
void FSequencer::AddActorsToBinding(FGuid InObjectBinding, const TArray<AActor*>& InActors)
{
if (!InActors.Num())
{
return;
}
UClass* ActorClass = nullptr;
int32 NumRuntimeObjects = 0;
TArrayView<TWeakObjectPtr<>> ObjectsInCurrentSequence = FindObjectsInCurrentSequence(InObjectBinding);
for (TWeakObjectPtr<> Ptr : ObjectsInCurrentSequence)
{
if (const AActor* Actor = Cast<AActor>(Ptr.Get()))
{
ActorClass = Actor->GetClass();
++NumRuntimeObjects;
}
}
FScopedTransaction AddSelectedToBinding(NSLOCTEXT("Sequencer", "AddSelectedToBinding", "Add Selected to Binding"));
UMovieSceneSequence* OwnerSequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene();
OwnerSequence->Modify();
OwnerMovieScene->Modify();
// Bind objects
int32 NumObjectsAdded = 0;
for (AActor* ActorToAdd : InActors)
{
if (!ObjectsInCurrentSequence.Contains(ActorToAdd))
{
if (ActorClass == nullptr || UClass::FindCommonBase(ActorToAdd->GetClass(), ActorClass) != nullptr)
{
if (ActorClass == nullptr)
{
ActorClass = ActorToAdd->GetClass();
}
ActorToAdd->Modify();
if (!OwnerMovieScene->FindPossessable(InObjectBinding)->BindSpawnableObject(GetFocusedTemplateID(), ActorToAdd, this))
{
OwnerSequence->BindPossessableObject(InObjectBinding, *ActorToAdd, GetPlaybackContext());
}
++NumObjectsAdded;
}
else
{
const FText NotificationText = FText::Format(LOCTEXT("UnableToAssignObject", "Cannot assign object {0}. Expected class {1}"), FText::FromString(ActorToAdd->GetName()), FText::FromString(ActorClass->GetName()));
FNotificationInfo Info(NotificationText);
Info.ExpireDuration = 3.f;
Info.bUseLargeFont = false;
FSlateNotificationManager::Get().AddNotification(Info);
}
}
}
// Update label
if (NumRuntimeObjects + NumObjectsAdded > 0)
{
FMovieScenePossessable* Possessable = OwnerMovieScene->FindPossessable(InObjectBinding);
if (Possessable && ActorClass != nullptr)
{
if (NumRuntimeObjects + NumObjectsAdded > 1)
{
FString NewLabel = ActorClass->GetName() + FString::Printf(TEXT(" (%d)"), NumRuntimeObjects + NumObjectsAdded);
Possessable->SetName(NewLabel);
}
else if (NumObjectsAdded > 0 && InActors.Num() > 0)
{
Possessable->SetName(InActors[0]->GetActorLabel());
}
Possessable->SetPossessedObjectClass(ActorClass);
}
}
RestorePreAnimatedState();
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void FSequencer::ReplaceBindingWithActors(FGuid InObjectBinding, const TArray<AActor*>& InActors)
{
FScopedTransaction ReplaceBindingWithActors(NSLOCTEXT("Sequencer", "ReplaceBindingWithActors", "Replace Binding with Actors"));
TArray<AActor*> ExistingActors;
for (TWeakObjectPtr<> Ptr : FindObjectsInCurrentSequence(InObjectBinding))
{
if (AActor* Actor = Cast<AActor>(Ptr.Get()))
{
if (!InActors.Contains(Actor))
{
ExistingActors.Add(Actor);
}
}
}
RemoveActorsFromBinding(InObjectBinding, ExistingActors);
TArray<AActor*> NewActors;
for (AActor* NewActor : InActors)
{
if (!ExistingActors.Contains(NewActor))
{
NewActors.Add(NewActor);
}
}
AddActorsToBinding(InObjectBinding, NewActors);
}
void FSequencer::RemoveActorsFromBinding(FGuid InObjectBinding, const TArray<AActor*>& InActors)
{
if (!InActors.Num())
{
return;
}
UClass* ActorClass = nullptr;
int32 NumRuntimeObjects = 0;
for (TWeakObjectPtr<> Ptr : FindObjectsInCurrentSequence(InObjectBinding))
{
if (const AActor* Actor = Cast<AActor>(Ptr.Get()))
{
ActorClass = Actor->GetClass();
++NumRuntimeObjects;
}
}
FScopedTransaction RemoveSelectedFromBinding(NSLOCTEXT("Sequencer", "RemoveSelectedFromBinding", "Remove Selected from Binding"));
UMovieSceneSequence* OwnerSequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = OwnerSequence->GetMovieScene();
TArray<UObject*> ObjectsToRemove;
for (AActor* ActorToRemove : InActors)
{
// Restore state on any components
for (UActorComponent* Component : TInlineComponentArray<UActorComponent*>(ActorToRemove))
{
if (Component)
{
PreAnimatedState.RestorePreAnimatedState(*Component);
}
}
// Restore state on the object itself
PreAnimatedState.RestorePreAnimatedState(*ActorToRemove);
ActorToRemove->Modify();
ObjectsToRemove.Add(ActorToRemove);
}
OwnerSequence->Modify();
OwnerMovieScene->Modify();
// Unbind objects
OwnerSequence->UnbindObjects(InObjectBinding, ObjectsToRemove, GetPlaybackContext());
// Update label
if (NumRuntimeObjects - ObjectsToRemove.Num() > 0)
{
FMovieScenePossessable* Possessable = OwnerMovieScene->FindPossessable(InObjectBinding);
if (Possessable && ActorClass != nullptr)
{
if (NumRuntimeObjects - ObjectsToRemove.Num() > 1)
{
FString NewLabel = ActorClass->GetName() + FString::Printf(TEXT(" (%d)"), NumRuntimeObjects - ObjectsToRemove.Num());
Possessable->SetName(NewLabel);
}
else if (ObjectsToRemove.Num() > 0 && InActors.Num() > 0)
{
Possessable->SetName(InActors[0]->GetActorLabel());
}
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void FSequencer::DeleteNode(TSharedRef<FViewModel> NodeToBeDeleted, const bool bKeepState)
{
// If this node is selected, delete all selected nodes
if (GetSelection().IsSelected(NodeToBeDeleted))
{
DeleteSelectedNodes(bKeepState);
}
else
{
const FScopedTransaction Transaction( NSLOCTEXT("Sequencer", "UndoDeletingObject", "Delete Node") );
bool bAnythingDeleted = OnRequestNodeDeleted(NodeToBeDeleted, bKeepState);
if ( bAnythingDeleted )
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemRemoved );
}
}
}
void FSequencer::DeleteSelectedNodes(const bool bKeepState)
{
using namespace UE::Sequencer;
TArray<TSharedPtr<FViewModel>> SelectedNodesCopy;
GetSelection().GetSelectedOutlinerItems(SelectedNodesCopy);
if (SelectedNodesCopy.Num() == 0)
{
return;
}
const FScopedTransaction Transaction( NSLOCTEXT("Sequencer", "UndoDeletingObject", "Delete Node") );
Selection.EmptySelectedOutlinerNodes();
bool bAnythingDeleted = false;
for (TSharedPtr<FViewModel>& SelectedNode : SelectedNodesCopy)
{
if (!SelectedNode->CastThisChecked<IOutlinerExtension>()->IsFilteredOut())
{
// Delete everything in the entire node
bAnythingDeleted |= OnRequestNodeDeleted( SelectedNode.ToSharedRef(), bKeepState );
}
// Null out the node, which should have the effect of completely destroying it
SelectedNode = nullptr;
}
if ( bAnythingDeleted )
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemRemoved );
}
}
void FSequencer::MoveNodeToFolder(TSharedRef<FViewModel> NodeToMove, UMovieSceneFolder* DestinationFolder)
{
using namespace UE::Sequencer;
TSharedPtr<FViewModel> ParentNode = NodeToMove->GetParent();
if (DestinationFolder == nullptr)
{
return;
}
DestinationFolder->Modify();
if (FFolderModel* FolderNode = NodeToMove->CastThis<FFolderModel>())
{
if (ParentNode.IsValid() && ParentNode->IsA<FFolderModel>())
{
FFolderModel* NodeParentFolder = ParentNode->CastThisChecked<FFolderModel>();
NodeParentFolder->GetFolder()->Modify();
NodeParentFolder->GetFolder()->RemoveChildFolder(FolderNode->GetFolder());
}
else
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (FocusedMovieScene)
{
FocusedMovieScene->Modify();
FocusedMovieScene->RemoveRootFolder(FolderNode->GetFolder());
}
}
DestinationFolder->AddChildFolder(FolderNode->GetFolder());
}
else if (ITrackExtension* TrackNode = NodeToMove->CastThis<ITrackExtension>())
{
if (ParentNode.IsValid() && ParentNode->IsA<FFolderModel>())
{
FFolderModel* NodeParentFolder = ParentNode->CastThisChecked<FFolderModel>();
NodeParentFolder->GetFolder()->Modify();
NodeParentFolder->GetFolder()->RemoveChildMasterTrack(TrackNode->GetTrack());
}
DestinationFolder->AddChildMasterTrack(TrackNode->GetTrack());
}
else if (IObjectBindingExtension* ObjectBindingNode = NodeToMove->CastThis<IObjectBindingExtension>())
{
if (ParentNode.IsValid() && ParentNode->IsA<FFolderModel>())
{
FFolderModel* NodeParentFolder = ParentNode->CastThisChecked<FFolderModel>();
NodeParentFolder->GetFolder()->Modify();
NodeParentFolder->GetFolder()->RemoveChildObjectBinding(ObjectBindingNode->GetObjectGuid());
}
DestinationFolder->AddChildObjectBinding(ObjectBindingNode->GetObjectGuid());
}
}
TArray<TSharedRef<UE::Sequencer::FViewModel>> FSequencer::GetSelectedNodesToMove()
{
using namespace UE::Sequencer;
TArray<TSharedRef<FViewModel>> NodesToMove;
// Build a list of the nodes we want to move.
TArray<TSharedRef<FViewModel>> SelectedItems;
GetSelection().GetSelectedOutlinerItems(SelectedItems);
for (TSharedRef<FViewModel> Node : SelectedItems)
{
// Only nodes that can be dragged can be moved in to a folder.
if (IDraggableOutlinerExtension* DraggableModel = Node->CastThis<IDraggableOutlinerExtension>())
{
if (DraggableModel->CanDrag())
{
NodesToMove.Add(Node);
}
}
}
if (!NodesToMove.Num())
{
return NodesToMove;
}
TArray<int32> NodesToRemove;
// Find nodes that are children of other nodes in the list
for (int32 NodeIndex = 0; NodeIndex < NodesToMove.Num(); ++NodeIndex)
{
TSharedPtr<FViewModel> Node = NodesToMove[NodeIndex];
for (TSharedRef<FViewModel> ParentNode : NodesToMove)
{
if (ParentNode == Node)
{
continue;
}
for (TSharedPtr<FViewModel> Child : ParentNode->GetDescendants(true))
{
if (Child == Node)
{
NodesToRemove.Add(NodeIndex);
break;
}
}
}
}
// Remove the nodes that are children of other nodes in the list, as moving the parent will already be relocating them
while (NodesToRemove.Num() > 0)
{
int32 NodeIndex = NodesToRemove.Pop();
NodesToMove.RemoveAt(NodeIndex);
}
return NodesToMove;
}
TArray<TSharedRef<UE::Sequencer::FViewModel> > FSequencer::GetSelectedNodesInFolders()
{
using namespace UE::Sequencer;
TArray<TSharedRef<FViewModel>> SelectedItems;
GetSelection().GetSelectedOutlinerItems(SelectedItems);
TArray<TSharedRef<FViewModel> > NodesToFolders;
for (TSharedRef<FViewModel> SelectedNode : SelectedItems)
{
TSharedPtr<FFolderModel> Folder = SelectedNode->FindAncestorOfType<FFolderModel>();
if (Folder.IsValid())
{
if (IObjectBindingExtension* ObjectBindingNode = SelectedNode->CastThis<IObjectBindingExtension>())
{
if (Folder->GetFolder()->GetChildObjectBindings().Contains(ObjectBindingNode->GetObjectGuid()))
{
NodesToFolders.Add(SelectedNode);
}
}
else if (ITrackExtension* TrackNode = SelectedNode->CastThis<ITrackExtension>())
{
if (TrackNode->GetTrack())
{
if (Folder->GetFolder()->GetChildMasterTracks().Contains(TrackNode->GetTrack()))
{
NodesToFolders.Add(SelectedNode);
}
}
}
}
}
return NodesToFolders;
}
void FSequencer::MoveSelectedNodesToFolder(UMovieSceneFolder* DestinationFolder)
{
using namespace UE::Sequencer;
if (!DestinationFolder)
{
return;
}
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
TArray<TSharedRef<FViewModel> > NodesToMove = GetSelectedNodesToMove();
for (TSharedRef<FViewModel> Node : NodesToMove)
{
// If this node is the destination folder, don't try to move it
if (FFolderModel* FolderNode = Node->CastThis<FFolderModel>())
{
if (FolderNode->GetFolder() == DestinationFolder)
{
NodesToMove.Remove(Node);
break;
}
}
}
if (!NodesToMove.Num())
{
return;
}
TArray<TArray<FString> > NodePathSplits;
int32 SharedPathLength = TNumericLimits<int32>::Max();
// Build a list of the paths for each node, split in to folder names
for (TSharedRef<FViewModel> Node : NodesToMove)
{
// Split the node's path in to segments
TArray<FString>& NodePath = NodePathSplits.AddDefaulted_GetRef();
IOutlinerExtension::GetPathName(*Node).ParseIntoArray(NodePath, TEXT("."));
// Shared path obviously won't be larger than the shortest path
SharedPathLength = FMath::Min(SharedPathLength, NodePath.Num() - 1);
}
// If we have more than one, find the deepest folder shared by all paths
if (NodePathSplits.Num() > 1)
{
// Since we are looking for the shared path, we can arbitrarily choose the first path to compare against
TArray<FString>& ShareNodePathSplit = NodePathSplits[0];
for (int NodeIndex = 1; NodeIndex < NodePathSplits.Num(); ++NodeIndex)
{
if (SharedPathLength == 0)
{
break;
}
// Since all paths are at least as long as the shortest, we don't need to bounds check the path splits
for (int PathSplitIndex = 0; PathSplitIndex < SharedPathLength; ++PathSplitIndex)
{
if (NodePathSplits[NodeIndex][PathSplitIndex].Compare(ShareNodePathSplit[PathSplitIndex]))
{
SharedPathLength = PathSplitIndex;
break;
}
}
}
}
UMovieSceneFolder* ParentFolder = nullptr;
TArray<FName> FolderPath;
// Walk up the shared path to find the deepest shared folder
for (int32 FolderPathIndex = 0; FolderPathIndex < SharedPathLength; ++FolderPathIndex)
{
FolderPath.Add(FName(*NodePathSplits[0][FolderPathIndex]));
FName DesiredFolderName = FolderPath[FolderPathIndex];
TArray<UMovieSceneFolder*> FoldersToSearch;
if (!ParentFolder)
{
FoldersToSearch = FocusedMovieScene->GetRootFolders();
}
else
{
FoldersToSearch = ParentFolder->GetChildFolders();
}
for (UMovieSceneFolder* Folder : FoldersToSearch)
{
if (Folder->GetFolderName() == DesiredFolderName)
{
ParentFolder = Folder;
break;
}
}
}
FScopedTransaction Transaction(LOCTEXT("MoveTracksToFolder", "Move to Folder"));
Selection.Empty();
// Find the path to the displaynode of our destination folder
FString DestinationFolderPath;
if (DestinationFolder)
{
TArray<TSharedRef<FViewModel>> AllNodes;
NodeTree->GetAllNodes(AllNodes);
for (TSharedRef<FViewModel> Node : AllNodes)
{
// If this node is the destination folder, don't try to move it
if (FFolderModel* FolderNode = Node->CastThis<FFolderModel>())
{
if (FolderNode->GetFolder() == DestinationFolder)
{
DestinationFolderPath = IOutlinerExtension::GetPathName(*Node);
// Expand the folders to our destination
TSharedPtr<FViewModel> ParentNode = Node;
while (ParentNode)
{
if (ParentNode->IsA<IOutlinerExtension>())
{
ParentNode->CastThisChecked<IOutlinerExtension>()->SetExpansion(true);
}
ParentNode = ParentNode->GetParent();
}
break;
}
}
}
}
for (int32 NodeIndex = 0; NodeIndex < NodesToMove.Num(); ++NodeIndex)
{
TSharedRef<FViewModel> Node = NodesToMove[NodeIndex];
TArray<FString>& NodePathSplit = NodePathSplits[NodeIndex];
// Reset the relative path
FolderPath.Reset(NodePathSplit.Num());
FString NewPath = DestinationFolderPath;
if (!NewPath.IsEmpty())
{
NewPath += TEXT(".");
}
// Append any relative path for the node
for (int32 FolderPathIndex = SharedPathLength; FolderPathIndex < NodePathSplit.Num() - 1; ++FolderPathIndex)
{
FolderPath.Add(FName(*NodePathSplit[FolderPathIndex]));
NewPath += NodePathSplit[FolderPathIndex] + TEXT(".");
}
NewPath += Node->CastThis<IOutlinerExtension>()->GetIdentifier().ToString();
UMovieSceneFolder* NodeDestinationFolder = CreateFoldersRecursively(FolderPath, 0, FocusedMovieScene, DestinationFolder, DestinationFolder->GetChildFolders());
MoveNodeToFolder(Node, NodeDestinationFolder);
SequencerWidget->AddAdditionalPathToSelectionSet(NewPath);
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void FSequencer::MoveSelectedNodesToNewFolder()
{
using namespace UE::Sequencer;
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
TArray<TSharedRef<FViewModel> > NodesToMove = GetSelectedNodesToMove();
if (!NodesToMove.Num())
{
return;
}
TArray<TArray<FString> > NodePathSplits;
int32 SharedPathLength = TNumericLimits<int32>::Max();
// Build a list of the paths for each node, split in to folder names
for (TSharedRef<FViewModel> Node : NodesToMove)
{
// Split the node's path in to segments
TArray<FString>& NodePath = NodePathSplits.AddDefaulted_GetRef();
IOutlinerExtension::GetPathName(*Node).ParseIntoArray(NodePath, TEXT("."));
// Shared path obviously won't be larger than the shortest path
SharedPathLength = FMath::Min(SharedPathLength, NodePath.Num() - 1);
}
// If we have more than one, find the deepest folder shared by all paths
if (NodePathSplits.Num() > 1)
{
// Since we are looking for the shared path, we can arbitrarily choose the first path to compare against
TArray<FString>& ShareNodePathSplit = NodePathSplits[0];
for (int NodeIndex = 1; NodeIndex < NodePathSplits.Num(); ++NodeIndex)
{
if (SharedPathLength == 0)
{
break;
}
// Since all paths are at least as long as the shortest, we don't need to bounds check the path splits
for (int PathSplitIndex = 0; PathSplitIndex < SharedPathLength; ++PathSplitIndex)
{
if (NodePathSplits[NodeIndex][PathSplitIndex].Compare(ShareNodePathSplit[PathSplitIndex]))
{
SharedPathLength = PathSplitIndex;
break;
}
}
}
}
UMovieSceneFolder* ParentFolder = nullptr;
TArray<FName> FolderPath;
// Walk up the shared path to find the deepest shared folder
for (int32 FolderPathIndex = 0; FolderPathIndex < SharedPathLength; ++FolderPathIndex)
{
FolderPath.Add(FName(*NodePathSplits[0][FolderPathIndex]));
FName DesiredFolderName = FolderPath[FolderPathIndex];
TArray<UMovieSceneFolder*> FoldersToSearch;
if (!ParentFolder)
{
FoldersToSearch = FocusedMovieScene->GetRootFolders();
}
else
{
FoldersToSearch = ParentFolder->GetChildFolders();
}
for (UMovieSceneFolder* Folder : FoldersToSearch)
{
if (Folder->GetFolderName() == DesiredFolderName)
{
ParentFolder = Folder;
break;
}
}
}
TArray<FName> ExistingFolderNames;
if (!ParentFolder)
{
for (UMovieSceneFolder* SiblingFolder : FocusedMovieScene->GetRootFolders())
{
ExistingFolderNames.Add(SiblingFolder->GetFolderName());
}
}
else
{
for (UMovieSceneFolder* SiblingFolder : ParentFolder->GetChildFolders())
{
ExistingFolderNames.Add(SiblingFolder->GetFolderName());
}
}
FString NewFolderPath;
for (FName PathSection : FolderPath)
{
NewFolderPath.Append(PathSection.ToString());
NewFolderPath.AppendChar('.');
}
FScopedTransaction Transaction(LOCTEXT("MoveTracksToNewFolder", "Move to New Folder"));
// Create SharedFolder
FName UniqueName = FSequencerUtilities::GetUniqueName(FName("New Folder"), ExistingFolderNames);
UMovieSceneFolder* SharedFolder = NewObject<UMovieSceneFolder>( FocusedMovieScene, NAME_None, RF_Transactional );
SharedFolder->SetFolderName(UniqueName);
NewFolderPath.Append(UniqueName.ToString());
FolderPath.Add(UniqueName);
int SharedFolderPathLen = FolderPath.Num();
if (!ParentFolder)
{
FocusedMovieScene->Modify();
FocusedMovieScene->AddRootFolder(SharedFolder);
}
else
{
ParentFolder->Modify();
ParentFolder->AddChildFolder(SharedFolder);
}
for (int32 NodeIndex = 0; NodeIndex < NodesToMove.Num() ; ++NodeIndex)
{
TSharedRef<FViewModel> Node = NodesToMove[NodeIndex];
TArray<FString>& NodePathSplit = NodePathSplits[NodeIndex];
// Reset to just the path to the shared folder
FolderPath.SetNum(SharedFolderPathLen);
// Append any relative path for the node
for (int32 FolderPathIndex = SharedPathLength; FolderPathIndex < NodePathSplit.Num() - 1; ++FolderPathIndex)
{
FolderPath.Add(FName(*NodePathSplit[FolderPathIndex]));
}
UMovieSceneFolder* DestinationFolder = CreateFoldersRecursively(FolderPath, 0, FocusedMovieScene, nullptr, FocusedMovieScene->GetRootFolders());
MoveNodeToFolder(Node, DestinationFolder);
}
// Set the newly created folder as our selection
Selection.Empty();
SequencerWidget->AddAdditionalPathToSelectionSet(NewFolderPath);
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void FSequencer::RemoveSelectedNodesFromFolders()
{
using namespace UE::Sequencer;
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
TArray<TSharedRef<FViewModel> > NodesToFolders = GetSelectedNodesInFolders();
if (!NodesToFolders.Num())
{
return;
}
FScopedTransaction Transaction(LOCTEXT("RemoveNodeFromFolder", "Remove from Folder"));
FocusedMovieScene->Modify();
for (TSharedRef<FViewModel> NodeInFolder : NodesToFolders)
{
TSharedPtr<FFolderModel> Folder = NodeInFolder->FindAncestorOfType<FFolderModel>();
if (Folder.IsValid())
{
if (IObjectBindingExtension* ObjectBindingNode = NodeInFolder->CastThis<IObjectBindingExtension>())
{
Folder->GetFolder()->RemoveChildObjectBinding(ObjectBindingNode->GetObjectGuid());
}
else if (ITrackExtension* TrackNode = NodeInFolder->CastThis<ITrackExtension>())
{
if (TrackNode->GetTrack())
{
Folder->GetFolder()->RemoveChildMasterTrack(TrackNode->GetTrack());
}
}
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void ExportObjectBindingsToText(const TArray<UMovieSceneCopyableBinding*>& ObjectsToExport, FString& ExportedText)
{
// Clear the mark state for saving.
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
FStringOutputDevice Archive;
const FExportObjectInnerContext Context;
// Export each of the selected nodes
UObject* LastOuter = nullptr;
for (UMovieSceneCopyableBinding* ObjectToExport : ObjectsToExport)
{
// The nodes should all be from the same scope
UObject* ThisOuter = ObjectToExport->GetOuter();
check((LastOuter == ThisOuter) || (LastOuter == nullptr));
LastOuter = ThisOuter;
// We can't use TextExportTransient on USTRUCTS (which our object contains) so we're going to manually null out some references before serializing them. These references are
// serialized manually into the archive, as the auto-serialization will only store a reference (to a privately owned object) which creates issues on deserialization. Attempting
// to deserialize these private objects throws a superflous error in the console that makes it look like things went wrong when they're actually OK and expected.
TArray<UMovieSceneTrack*> OldTracks = ObjectToExport->Binding.StealTracks(nullptr);
UObject* OldSpawnableTemplate = ObjectToExport->Spawnable.GetObjectTemplate();
ObjectToExport->Spawnable.SetObjectTemplate(nullptr);
UExporter::ExportToOutputDevice(&Context, ObjectToExport, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, ThisOuter);
// Restore the references (as we don't want to modify the original in the event of a copy operation!)
ObjectToExport->Binding.SetTracks(MoveTemp(OldTracks), nullptr);
ObjectToExport->Spawnable.SetObjectTemplate(OldSpawnableTemplate);
// We manually export the object template for the same private-ownership reason as above. Templates need to be re-created anyways as each Spawnable contains its own copy of the template.
if (ObjectToExport->SpawnableObjectTemplate)
{
UExporter::ExportToOutputDevice(&Context, ObjectToExport->SpawnableObjectTemplate, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited);
}
}
ExportedText = Archive;
}
class FObjectBindingTextFactory : public FCustomizableTextObjectFactory
{
public:
FObjectBindingTextFactory(FSequencer& InSequencer)
: FCustomizableTextObjectFactory(GWarn)
, SequencerPtr(&InSequencer)
{
}
// FCustomizableTextObjectFactory implementation
virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override
{
if (InObjectClass->IsChildOf<UMovieSceneCopyableBinding>())
{
return true;
}
return SequencerPtr->GetSpawnRegister().CanSpawnObject(InObjectClass);
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
if (NewObject->IsA<UMovieSceneCopyableBinding>())
{
UMovieSceneCopyableBinding* CopyableBinding = Cast<UMovieSceneCopyableBinding>(NewObject);
NewCopyableBindings.Add(CopyableBinding);
}
else
{
NewSpawnableObjectTemplates.Add(NewObject);
}
}
public:
TArray<UMovieSceneCopyableBinding*> NewCopyableBindings;
TArray<UObject*> NewSpawnableObjectTemplates;
private:
FSequencer* SequencerPtr;
};
void FSequencer::ImportObjectBindingsFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneCopyableBinding*>& ImportedObjects)
{
UPackage* TempPackage = NewObject<UPackage>(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient);
TempPackage->AddToRoot();
// Turn the text buffer into objects
FObjectBindingTextFactory Factory(*this);
Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport);
ImportedObjects = Factory.NewCopyableBindings;
// We had to explicitly serialize object templates due to them being a reference to a privately owned object. We now deserialize these object template copies
// and match them up with their MovieSceneCopyableBinding again.
int32 SpawnableObjectTemplateIndex = 0;
for (auto ImportedObject : ImportedObjects)
{
if (ImportedObject->Spawnable.GetGuid().IsValid() && SpawnableObjectTemplateIndex < Factory.NewSpawnableObjectTemplates.Num())
{
// This Spawnable Object Template is owned by our transient package, so you'll need to change the owner if you want to keep it later.
ImportedObject->SpawnableObjectTemplate = Factory.NewSpawnableObjectTemplates[SpawnableObjectTemplateIndex++];
}
}
// Remove the temp package from the root now that it has served its purpose
TempPackage->RemoveFromRoot();
}
TArray<TSharedPtr<FMovieSceneClipboard>> GClipboardStack;
void FSequencer::CopySelectedObjects(TArray<TSharedPtr<UE::Sequencer::FObjectBindingModel>>& ObjectNodes, const TArray<UMovieSceneFolder*>& Folders, FString& ExportedText)
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
// Gather guids for the object nodes and any child object nodes
TSet<FGuid> GuidsToCopy;
TMap<FGuid, UMovieSceneFolder*> GuidToFolder;
for (TSharedPtr<FObjectBindingModel> ObjectNode : ObjectNodes)
{
GuidsToCopy.Add(ObjectNode->GetObjectGuid());
TSharedPtr<FFolderModel> FolderNode = ObjectNode->FindAncestorOfType<FFolderModel>();
if (FolderNode.IsValid() && Folders.Contains(FolderNode->GetFolder()))
{
GuidToFolder.Add(ObjectNode->GetObjectGuid(), FolderNode->GetFolder());
}
TSet<TSharedRef<FViewModel> > DescendantNodes;
SequencerHelpers::GetDescendantNodes(ObjectNode.ToSharedRef(), DescendantNodes);
for (auto DescendantNode : DescendantNodes)
{
if (FObjectBindingModel* DescendantObjectNode = DescendantNode->CastThis<FObjectBindingModel>())
{
GuidsToCopy.Add(DescendantObjectNode->GetObjectGuid());
TSharedPtr<FFolderModel> DescendantFolderNode = DescendantObjectNode->FindAncestorOfType<FFolderModel>();
if (DescendantFolderNode.IsValid())
{
GuidToFolder.Add(DescendantObjectNode->GetObjectGuid(), DescendantFolderNode->GetFolder());
}
}
}
}
// Export each of the bindings
TArray<UMovieSceneCopyableBinding*> CopyableBindings;
for (auto ObjectBinding : GuidsToCopy)
{
UMovieSceneCopyableBinding *CopyableBinding = NewObject<UMovieSceneCopyableBinding>(GetTransientPackage(), UMovieSceneCopyableBinding::StaticClass(), NAME_None, RF_Transient);
CopyableBindings.Add(CopyableBinding);
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBinding);
if (Possessable)
{
CopyableBinding->Possessable = *Possessable;
}
else
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBinding);
if (Spawnable)
{
CopyableBinding->Spawnable = *Spawnable;
// We manually serialize the spawnable object template so that it's not a reference to a privately owned object. Spawnables all have unique copies of their template objects anyways.
// Object Templates are re-created on paste (based on these templates) with the correct ownership set up.
CopyableBinding->SpawnableObjectTemplate = Spawnable->GetObjectTemplate();
}
}
const FMovieSceneBinding* Binding = MovieScene->FindBinding(ObjectBinding);
if (Binding)
{
CopyableBinding->Binding = *Binding;
for (auto Track : Binding->GetTracks())
{
// Tracks suffer from the same issues as Spawnable's Object Templates (reference to a privately owned object). We'll manually serialize the tracks to copy them,
// and then restore them on paste.
UMovieSceneTrack* DuplicatedTrack = Cast<UMovieSceneTrack>(StaticDuplicateObject(Track, CopyableBinding));
CopyableBinding->Tracks.Add(DuplicatedTrack);
}
}
if (GuidToFolder.Contains(ObjectBinding))
{
UMovieSceneFolder::CalculateFolderPath(GuidToFolder[ObjectBinding], Folders, CopyableBinding->FolderPath);
}
}
if (CopyableBindings.Num() > 0)
{
ExportObjectBindingsToText(CopyableBindings, /*out*/ ExportedText);
// Make sure to clear the clipboard for the keys
GClipboardStack.Empty();
}
}
void FSequencer::CopySelectedTracks(TArray<TSharedPtr<FViewModel>>& TrackNodes, const TArray<UMovieSceneFolder*>& Folders, FString& ExportedText)
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
TArray<UObject*> CopyableObjects;
for (TSharedPtr<FViewModel> TrackNode : TrackNodes)
{
bool bIsParentSelected = false;
TSharedPtr<FViewModel> ParentNode = TrackNode->GetParent();
while (ParentNode.IsValid() && !ParentNode->IsA<FFolderModel>())
{
if (Selection.GetSelectedOutlinerItems().Contains(ParentNode.ToSharedRef()))
{
bIsParentSelected = true;
break;
}
ParentNode = ParentNode->GetParent();
}
if (!bIsParentSelected)
{
// If this is a subtrack, only copy the sections that belong to this row. otherwise copying the entire track will copy all the sections across all the rows
if (FTrackRowModel* TrackRowNode = TrackNode->CastThis<FTrackRowModel>())
{
for (UMovieSceneSection* Section : TrackRowNode->GetTrack()->GetAllSections())
{
if (Section && Section->GetRowIndex() == TrackRowNode->GetRowIndex())
{
CopyableObjects.Add(Section);
}
}
}
else
{
UMovieSceneCopyableTrack* CopyableTrack = NewObject<UMovieSceneCopyableTrack>(GetTransientPackage(), UMovieSceneCopyableTrack::StaticClass(), NAME_None, RF_Transient);
CopyableObjects.Add(CopyableTrack);
UMovieSceneTrack* DuplicatedTrack = Cast<UMovieSceneTrack>(StaticDuplicateObject(TrackNode->CastThisChecked<ITrackExtension>()->GetTrack(), CopyableTrack));
CopyableTrack->Track = DuplicatedTrack;
CopyableTrack->bIsAMasterTrack = MovieScene->IsAMasterTrack(*TrackNode->CastThisChecked<ITrackExtension>()->GetTrack());
CopyableTrack->bIsACameraCutTrack = TrackNode->CastThisChecked<ITrackExtension>()->GetTrack()->IsA<UMovieSceneCameraCutTrack>();
TSharedPtr<FFolderModel> FolderNode = TrackNode->FindAncestorOfType<FFolderModel>();
if (FolderNode.IsValid() && Folders.Contains(FolderNode->GetFolder()))
{
UMovieSceneFolder::CalculateFolderPath(FolderNode->GetFolder(), Folders, CopyableTrack->FolderPath);
}
}
}
}
if (CopyableObjects.Num())
{
ExportObjectsToText(CopyableObjects, /*out*/ ExportedText);
// Make sure to clear the clipboard for the keys
GClipboardStack.Empty();
}
}
void FSequencer::CopySelectedFolders(const TArray<UMovieSceneFolder*>& Folders, FString& ExportedText)
{
if (Folders.Num() > 0)
{
TArray<UObject*> Objects;
for (UMovieSceneFolder* Folder : Folders)
{
Objects.Add(Folder);
}
ExportObjectsToText(Objects, /*out*/ ExportedText);
// Make sure to clear the clipboard for the keys
GClipboardStack.Empty();
}
}
void FSequencer::ExportObjectsToText(const TArray<UObject*>& ObjectsToExport, FString& ExportedText)
{
// Clear the mark state for saving.
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
FStringOutputDevice Archive;
const FExportObjectInnerContext Context;
// Export each of the selected nodes
UObject* LastOuter = nullptr;
for (UObject* ObjectToExport : ObjectsToExport)
{
// The nodes should all be from the same scope
UObject* ThisOuter = ObjectToExport->GetOuter();
if (LastOuter != nullptr && ThisOuter != LastOuter)
{
UE_LOG(LogSequencer, Warning, TEXT("Cannot copy objects from different outers. Only copying from %s"), *LastOuter->GetName());
continue;
}
LastOuter = ThisOuter;
UExporter::ExportToOutputDevice(&Context, ObjectToExport, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, ThisOuter);
}
ExportedText = Archive;
}
bool FSequencer::DoPaste(bool bClearSelection)
{
if (IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
// If we cancel the paste due to being read-only, count that as having handled the paste operation
return true;
}
// Call into the active customizations.
ESequencerPasteSupport PasteSupport = ESequencerPasteSupport::All;
for (const FOnSequencerPaste& Callback : OnPaste)
{
const ESequencerPasteSupport CurSupport = Callback.Execute();
PasteSupport = (PasteSupport & CurSupport);
}
if (PasteSupport == ESequencerPasteSupport::None)
{
// We handled the paste operation but we didn't support doing anything.
return true;
}
// Grab the text to paste from the clipboard
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
UMovieSceneFolder* ParentFolder = SelectedParentFolders.Num() > 0 ? SelectedParentFolders[0] : nullptr;
TArray<FNotificationInfo> PasteErrors;
bool bAnythingPasted = false;
TArray<UMovieSceneFolder*> PastedFolders;
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::Folders))
{
bAnythingPasted |= PasteFolders(TextToImport, ParentFolder, PastedFolders, PasteErrors);
}
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::ObjectBindings))
{
bAnythingPasted |= PasteObjectBindings(TextToImport, ParentFolder, PastedFolders, PasteErrors, bClearSelection);
}
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::Tracks))
{
bAnythingPasted |= PasteTracks(TextToImport, ParentFolder, PastedFolders, PasteErrors, bClearSelection);
}
if (!bAnythingPasted)
{
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::Sections))
{
bAnythingPasted |= PasteSections(TextToImport, PasteErrors);
}
}
if (!bAnythingPasted)
{
for (auto NotificationInfo : PasteErrors)
{
NotificationInfo.bUseLargeFont = false;
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
}
}
return bAnythingPasted;
}
bool FSequencer::PasteFolders(const FString& TextToImport, UMovieSceneFolder* InParentFolder, TArray<UMovieSceneFolder*>& OutFolders, TArray<FNotificationInfo>& PasteErrors)
{
TArray<UMovieSceneFolder*> ImportedFolders;
ImportFoldersFromText(TextToImport, ImportedFolders);
if (ImportedFolders.Num() == 0)
{
return false;
}
UMovieSceneSequence* OwnerSequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
MovieScene->Modify();
for (UMovieSceneFolder* CopiedFolder : ImportedFolders)
{
CopiedFolder->Rename(nullptr, MovieScene);
OutFolders.Add(CopiedFolder);
// Clear the folder contents, those relationships will be made when the tracks are pasted
CopiedFolder->ClearChildMasterTracks();
CopiedFolder->ClearChildObjectBindings();
bool bHasParent = false;
for (UMovieSceneFolder* ImportedParentFolder : ImportedFolders)
{
if (ImportedParentFolder != CopiedFolder)
{
if (ImportedParentFolder->GetChildFolders().Contains(CopiedFolder))
{
bHasParent = true;
break;
}
}
}
if (!bHasParent)
{
if (InParentFolder)
{
InParentFolder->AddChildFolder(CopiedFolder);
}
else
{
MovieScene->AddRootFolder(CopiedFolder);
}
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
return true;
}
bool FSequencer::PasteObjectBindings(const FString& TextToImport, UMovieSceneFolder* InParentFolder, const TArray<UMovieSceneFolder*>& InFolders, TArray<FNotificationInfo>& PasteErrors, bool bClearSelection)
{
using namespace UE::Sequencer;
UWorld* World = Cast<UWorld>(GetPlaybackContext());
UMovieSceneSequence* OwnerSequence = GetFocusedMovieSceneSequence();
UObject* BindingContext = GetPlaybackContext();
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
TMap<FGuid, FGuid> OldToNewGuidMap;
TArray<FGuid> PossessableGuids;
TArray<FGuid> SpawnableGuids;
TMap<FGuid, UMovieSceneFolder*> GuidToFolderMap;
TArray<FMovieSceneBinding> BindingsPasted;
TArray<TSharedRef<FViewModel>> SelectedNodes;
Selection.GetSelectedOutlinerItems(SelectedNodes);
TArray<FGuid> SelectedParentGuids;
if (!bClearSelection)
{
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
if (FObjectBindingModel* ObjectNode = Node->CastThis<FObjectBindingModel>())
{
SelectedParentGuids.Add(ObjectNode->GetObjectGuid());
}
}
}
int32 NumTargets = 1;
if (SelectedParentGuids.Num() > 1)
{
NumTargets = SelectedParentGuids.Num();
}
for (int32 TargetIndex = 0; TargetIndex < NumTargets; ++TargetIndex)
{
TArray<UMovieSceneCopyableBinding*> ImportedBindings;
ImportObjectBindingsFromText(TextToImport, ImportedBindings);
if (ImportedBindings.Num() == 0)
{
return false;
}
for (UMovieSceneCopyableBinding* CopyableBinding : ImportedBindings)
{
// Clear transient flags on the imported tracks
for (UMovieSceneTrack* CopiedTrack : CopyableBinding->Tracks)
{
CopiedTrack->ClearFlags(RF_Transient);
TArray<UObject*> Subobjects;
GetObjectsWithOuter(CopiedTrack, Subobjects);
for (UObject* Subobject : Subobjects)
{
Subobject->ClearFlags(RF_Transient);
}
}
UMovieSceneFolder* ParentFolder = InParentFolder;
if (CopyableBinding->FolderPath.Num() > 0)
{
ParentFolder = UMovieSceneFolder::GetFolderWithPath(CopyableBinding->FolderPath, InFolders, ParentFolder ? ParentFolder->GetChildFolders() : MovieScene->GetRootFolders());
}
if (CopyableBinding->Possessable.GetGuid().IsValid())
{
FGuid NewGuid = FGuid::NewGuid();
FMovieSceneBinding NewBinding(NewGuid, CopyableBinding->Binding.GetName(), CopyableBinding->Tracks);
FMovieScenePossessable NewPossessable = CopyableBinding->Possessable;
NewPossessable.SetGuid(NewGuid);
MovieScene->AddPossessable(NewPossessable, NewBinding);
OldToNewGuidMap.Add(CopyableBinding->Possessable.GetGuid(), NewGuid);
BindingsPasted.Add(NewBinding);
PossessableGuids.Add(NewGuid);
if (ParentFolder)
{
GuidToFolderMap.Add(NewGuid, ParentFolder);
}
if (FMovieScenePossessable* Possessable = MovieScene->FindPossessable(NewGuid))
{
if (TargetIndex < SelectedParentGuids.Num())
{
Possessable->SetParent(SelectedParentGuids[TargetIndex], MovieScene);
}
}
TArray<AActor*> ActorsToDuplicate;
for (auto RuntimeObject : FindBoundObjects(CopyableBinding->Possessable.GetGuid(), ActiveTemplateIDs.Top()))
{
AActor* Actor = Cast<AActor>(RuntimeObject.Get());
if (Actor)
{
ActorsToDuplicate.Add(Actor);
}
}
TArray<AActor*> DuplicatedActors;
if (ActorsToDuplicate.Num() != 0)
{
GEditor->SelectNone(false, true);
for (AActor* ActorToDuplicate : ActorsToDuplicate)
{
GEditor->SelectActor(ActorToDuplicate, true, false, false);
}
// Duplicate the bound actors
GEditor->edactDuplicateSelected(World->GetCurrentLevel(), false);
// Duplicating the bound actor through GEditor, edits the copy/paste clipboard. This is not desired from the user's
// point of view since the user didn't explicitly invoke the copy operation. Instead, restore the copied contents
// of the clipboard after duplicating the actor
FPlatformApplicationMisc::ClipboardCopy(*TextToImport);
USelection* ActorSelection = GEditor->GetSelectedActors();
TArray<TWeakObjectPtr<AActor> > SelectedActors;
for (FSelectionIterator Iter(*ActorSelection); Iter; ++Iter)
{
AActor* Actor = Cast<AActor>(*Iter);
if (Actor)
{
DuplicatedActors.Add(Actor);
}
}
// Bind the duplicated actors
if (DuplicatedActors.Num())
{
ReplaceBindingWithActors(NewGuid, DuplicatedActors);
}
}
}
else if (CopyableBinding->Spawnable.GetGuid().IsValid())
{
// We need to let the sequence create the spawnable so that it has everything set up properly internally.
// This is required to get spawnables with the correct references to object templates, object templates with
// correct owners, etc. However, making a new spawnable also creates the binding for us - this is a problem
// because we need to use our binding (which has tracks associated with it). To solve this, we let it create
// an object template based off of our (transient package owned) template, then find the newly created binding
// and update it.
FGuid NewGuid = MakeNewSpawnable(*CopyableBinding->SpawnableObjectTemplate, nullptr, false);
FMovieSceneBinding NewBinding(NewGuid, CopyableBinding->Binding.GetName(), CopyableBinding->Tracks);
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(NewGuid);
// Copy the name of the original spawnable too.
Spawnable->SetName(CopyableBinding->Spawnable.GetName());
// Clear the transient flags on the copyable binding before assigning to the new spawnable
for (auto Track : NewBinding.GetTracks())
{
Track->ClearFlags(RF_Transient);
for (auto Section : Track->GetAllSections())
{
Section->ClearFlags(RF_Transient);
}
}
// Replace the auto-generated binding with our deserialized bindings (which has our tracks)
MovieScene->ReplaceBinding(NewGuid, NewBinding);
OldToNewGuidMap.Add(CopyableBinding->Spawnable.GetGuid(), NewGuid);
BindingsPasted.Add(NewBinding);
SpawnableGuids.Add(NewGuid);
if (ParentFolder)
{
GuidToFolderMap.Add(NewGuid, ParentFolder);
}
}
}
}
// Fix up parent guids
for (auto PossessableGuid : PossessableGuids)
{
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid);
if (Possessable && OldToNewGuidMap.Contains(Possessable->GetParent()) && PossessableGuid != OldToNewGuidMap[Possessable->GetParent()])
{
Possessable->SetParent(OldToNewGuidMap[Possessable->GetParent()], MovieScene);
}
}
// Fix possessable actor bindings
for (int32 PossessableGuidIndex = 0; PossessableGuidIndex < PossessableGuids.Num(); ++PossessableGuidIndex)
{
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuids[PossessableGuidIndex]);
UWorld* PlaybackContext = Cast<UWorld>(GetPlaybackContext());
if (Possessable && PlaybackContext)
{
for (TActorIterator<AActor> ActorItr(PlaybackContext); ActorItr; ++ActorItr)
{
AActor *Actor = *ActorItr;
if (Actor && Actor->GetActorLabel() == *Possessable->GetName())
{
FGuid ExistingGuid = FindObjectId(*Actor, ActiveTemplateIDs.Top());
if (!ExistingGuid.IsValid())
{
FGuid NewGuid = FSequencerUtilities::DoAssignActor(this, Actor, Possessable->GetGuid());
// If assigning produces a new guid, update the possessable guids and the bindings pasted data
if (NewGuid.IsValid())
{
for (auto BindingPasted : BindingsPasted)
{
if (BindingPasted.GetObjectGuid() == PossessableGuids[PossessableGuidIndex])
{
BindingPasted.SetObjectGuid(NewGuid);
}
}
if (GuidToFolderMap.Contains(PossessableGuids[PossessableGuidIndex]))
{
GuidToFolderMap.Add(NewGuid, GuidToFolderMap[PossessableGuids[PossessableGuidIndex]]);
GuidToFolderMap.Remove(PossessableGuids[PossessableGuidIndex]);
}
PossessableGuids[PossessableGuidIndex] = NewGuid;
}
}
}
}
}
}
// Set up folders
for (auto PossessableGuid : PossessableGuids)
{
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid);
if (Possessable && !Possessable->GetParent().IsValid())
{
if (GuidToFolderMap.Contains(PossessableGuid))
{
GuidToFolderMap[PossessableGuid]->AddChildObjectBinding(PossessableGuid);
}
}
}
for (auto SpawnableGuid : SpawnableGuids)
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(SpawnableGuid);
if (Spawnable)
{
if (GuidToFolderMap.Contains(SpawnableGuid))
{
GuidToFolderMap[SpawnableGuid]->AddChildObjectBinding(SpawnableGuid);
}
}
}
OnMovieSceneBindingsPastedDelegate.Broadcast(BindingsPasted);
// Refresh all immediately so that spawned actors will be generated immediately
ForceEvaluate();
// Fix possessable component bindings
for (auto PossessableGuid : PossessableGuids)
{
// If a possessable guid does not have any bound objects, they might be
// possessable components for spawnables, so they need to be remapped
if (FindBoundObjects(PossessableGuid, ActiveTemplateIDs.Top()).Num() == 0)
{
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid);
if (Possessable)
{
FGuid ParentGuid = Possessable->GetParent();
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(ParentGuid, ActiveTemplateIDs.Top()))
{
if (AActor* SpawnedActor = Cast<AActor>(WeakObject.Get()))
{
for (UActorComponent* Component : SpawnedActor->GetComponents())
{
if (Component->GetName() == Possessable->GetName())
{
OwnerSequence->BindPossessableObject(PossessableGuid, *Component, SpawnedActor);
break;
}
}
}
}
// If the parent doesn't actually exist, clear it.
FMovieScenePossessable* PossessableParent = MovieScene->FindPossessable(ParentGuid);
FMovieSceneSpawnable* SpawnableParent = MovieScene->FindSpawnable(ParentGuid);
if (!PossessableParent && !SpawnableParent)
{
Possessable->SetParent(FGuid(), MovieScene);
}
else if (SpawnableParent)
{
SpawnableParent->AddChildPossessable(PossessableGuid);
}
}
}
}
// Remap bindings in sections (ie. attach tracks)
for (TPair<FGuid, FGuid> GuidPair : OldToNewGuidMap)
{
FSequencerUtilities::UpdateBindingIDs(this, CompiledDataManager, GuidPair.Key, GuidPair.Value);
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
return true;
}
bool FSequencer::PasteTracks(const FString& TextToImport, UMovieSceneFolder* InParentFolder, const TArray<UMovieSceneFolder*>& InFolders, TArray<FNotificationInfo>& PasteErrors, bool bClearSelection)
{
using namespace UE::Sequencer;
TArray<UMovieSceneCopyableTrack*> ImportedTracks;
FSequencer::ImportTracksFromText(TextToImport, ImportedTracks);
if (ImportedTracks.Num() == 0)
{
return false;
}
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
UObject* BindingContext = GetPlaybackContext();
TArray<TSharedRef<FViewModel>> SelectedNodes;
Selection.GetSelectedOutlinerItems(SelectedNodes);
TArray<TSharedPtr<FObjectBindingModel>> ObjectNodes;
if (!bClearSelection)
{
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
if (TSharedPtr<FObjectBindingModel> ObjectNode = Node->CastThisShared<FObjectBindingModel>())
{
ObjectNodes.Add(ObjectNode);
}
}
}
int32 NumMasterOrCameraCutTracks = 0;
int32 NumTracks = 0;
for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks)
{
if (CopyableTrack->bIsAMasterTrack || CopyableTrack->bIsACameraCutTrack)
{
++NumMasterOrCameraCutTracks;
}
else
{
++NumTracks;
}
}
int32 NumTracksPasted = 0;
int32 NumMasterOrCameraCutTracksPasted = 0;
if (ObjectNodes.Num())
{
for (TSharedPtr<FObjectBindingModel> ObjectNode : ObjectNodes)
{
FGuid ObjectGuid = ObjectNode->GetObjectGuid();
TArray<UMovieSceneCopyableTrack*> NewTracks;
FSequencer::ImportTracksFromText(TextToImport, NewTracks);
for (UMovieSceneCopyableTrack* CopyableTrack : NewTracks)
{
if (!CopyableTrack->bIsAMasterTrack && !CopyableTrack->bIsACameraCutTrack)
{
UMovieSceneTrack* NewTrack = CopyableTrack->Track;
NewTrack->ClearFlags(RF_Transient);
TArray<UObject*> Subobjects;
GetObjectsWithOuter(NewTrack, Subobjects);
for (UObject* Subobject : Subobjects)
{
Subobject->ClearFlags(RF_Transient);
}
// Remove tracks with the same name before adding
for (const FMovieSceneBinding& Binding : MovieScene->GetBindings())
{
if (Binding.GetObjectGuid() == ObjectGuid)
{
// Tracks of the same class should be unique per name.
for (UMovieSceneTrack* Track : Binding.GetTracks())
{
if (Track->GetClass() == NewTrack->GetClass() && Track->GetTrackName() == NewTrack->GetTrackName() && Track->GetDisplayName().IdenticalTo(NewTrack->GetDisplayName()))
{
// If a track of the same class and name exists, remove it so the new track replaces it
MovieScene->RemoveTrack(*Track);
break;
}
}
}
}
if (!MovieScene->AddGivenTrack(NewTrack, ObjectGuid))
{
continue;
}
else
{
++NumTracksPasted;
}
}
}
}
}
// Add as master track or set camera cut track
for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks)
{
if (CopyableTrack->bIsAMasterTrack || CopyableTrack->bIsACameraCutTrack)
{
UMovieSceneTrack* NewTrack = CopyableTrack->Track;
NewTrack->ClearFlags(RF_Transient);
TArray<UObject*> Subobjects;
GetObjectsWithOuter(NewTrack, Subobjects);
for (UObject* Subobject : Subobjects)
{
Subobject->ClearFlags(RF_Transient);
}
UMovieSceneFolder* ParentFolder = InParentFolder;
if (CopyableTrack->FolderPath.Num() > 0)
{
ParentFolder = UMovieSceneFolder::GetFolderWithPath(CopyableTrack->FolderPath, InFolders, ParentFolder ? ParentFolder->GetChildFolders() : MovieScene->GetRootFolders());
}
if (NewTrack->IsA(UMovieSceneCameraCutTrack::StaticClass()))
{
MovieScene->SetCameraCutTrack(NewTrack);
if (ParentFolder != nullptr)
{
ParentFolder->AddChildMasterTrack(NewTrack);
}
++NumMasterOrCameraCutTracksPasted;
}
else
{
if (MovieScene->AddGivenMasterTrack(NewTrack))
{
if (ParentFolder != nullptr)
{
ParentFolder->AddChildMasterTrack(NewTrack);
}
}
++NumMasterOrCameraCutTracksPasted;
}
}
}
if (NumMasterOrCameraCutTracksPasted < NumMasterOrCameraCutTracks)
{
FNotificationInfo Info(LOCTEXT("PasteTracks_NoMasterTracks", "Can't paste track. Master track could not be pasted"));
PasteErrors.Add(Info);
}
if (NumTracksPasted < NumTracks)
{
FNotificationInfo Info(LOCTEXT("PasteSections_NoSelectedObjects", "Can't paste track. No selected objects to paste tracks onto"));
PasteErrors.Add(Info);
}
if ((NumMasterOrCameraCutTracksPasted + NumTracksPasted) > 0)
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
return true;
}
return false;
}
void GetSupportedTracks(TSharedRef<UE::Sequencer::FViewModel> DisplayNode, const TArray<UMovieSceneSection*>& ImportedSections, TArray<TSharedRef<UE::Sequencer::FViewModel>>& TracksToPasteOnto)
{
using namespace UE::Sequencer;
ITrackExtension* TrackNode = DisplayNode->CastThis<ITrackExtension>();
if (TrackNode == nullptr)
{
return;
}
if (TracksToPasteOnto.Contains(DisplayNode))
{
return;
}
UMovieSceneTrack* Track = TrackNode->GetTrack();
if (Track)
{
for (int32 SectionIndex = 0; SectionIndex < ImportedSections.Num(); ++SectionIndex)
{
UMovieSceneSection* Section = ImportedSections[SectionIndex];
if (Track->SupportsType(Section->GetClass()))
{
TracksToPasteOnto.Add(DisplayNode);
return;
}
}
}
}
bool FSequencer::PasteSections(const FString& TextToImport, TArray<FNotificationInfo>& PasteErrors)
{
using namespace UE::Sequencer;
FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
// First import as a track and extract sections to allow for copying track contents to another track
TArray<UMovieSceneCopyableTrack*> ImportedTracks;
FSequencer::ImportTracksFromText(TextToImport, ImportedTracks);
TArray<UMovieSceneSection*> ImportedSections;
for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks)
{
for (UMovieSceneSection* CopyableSection : CopyableTrack->Track->GetAllSections())
{
ImportedSections.Add(CopyableSection);
}
}
// Otherwise, import as sections
if (ImportedSections.Num() == 0)
{
FSequencer::ImportSectionsFromText(TextToImport, ImportedSections);
}
if (ImportedSections.Num() == 0)
{
return false;
}
TArray<TSharedRef<FViewModel>> SelectedNodes;
Selection.GetSelectedOutlinerItems(SelectedNodes);
if (SelectedNodes.Num() == 0)
{
for (const TViewModelPtr<IOutlinerExtension>& RootNode : NodeTree->GetRootNodes())
{
TSet<TWeakObjectPtr<UMovieSceneSection>> Sections;
SequencerHelpers::GetAllSections(RootNode.AsModel(), Sections);
for (TWeakObjectPtr<UMovieSceneSection> Section : Sections)
{
if (Selection.GetSelectedSections().Contains(Section.Get()))
{
SelectedNodes.Add(RootNode.AsModel().ToSharedRef());
break;
}
}
}
}
if (SelectedNodes.Num() == 0)
{
FNotificationInfo Info(LOCTEXT("PasteSections_NoSelectedTracks", "Can't paste section. No selected tracks to paste sections onto"));
PasteErrors.Add(Info);
return false;
}
FFrameNumber LocalTime = GetLocalTime().Time.GetFrame();
TOptional<FFrameNumber> FirstFrame;
for (UMovieSceneSection* Section : ImportedSections)
{
if (Section->HasStartFrame())
{
if (FirstFrame.IsSet())
{
if (FirstFrame.GetValue() > Section->GetInclusiveStartFrame())
{
FirstFrame = Section->GetInclusiveStartFrame();
}
}
else
{
FirstFrame = Section->GetInclusiveStartFrame();
}
}
}
// Check if any of the selected nodes supports pasting this type of section
TArray<TSharedRef<FViewModel>> TracksToPasteOnto;
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
GetSupportedTracks(Node, ImportedSections, TracksToPasteOnto);
}
// Otherwise, look at all child nodes for supported tracks
if (TracksToPasteOnto.Num() == 0)
{
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
TSet<TSharedRef<FViewModel> > DescendantNodes;
SequencerHelpers::GetDescendantNodes(Node, DescendantNodes);
for (TSharedRef<FViewModel> DescendantNode : DescendantNodes)
{
// Don't automatically paste onto subtracks because that would lead to multiple paste destinations
if (DescendantNode->IsA<FTrackRowModel>())
{
continue;
}
GetSupportedTracks(DescendantNode, ImportedSections, TracksToPasteOnto);
}
}
}
TArray<UMovieSceneSection*> NewSections;
TArray<int32> SectionIndicesImported;
for (TSharedRef<FViewModel> TrackNode : TracksToPasteOnto)
{
UMovieSceneTrack* Track = TrackNode->CastThisChecked<ITrackExtension>()->GetTrack();
for (int32 SectionIndex = 0; SectionIndex < ImportedSections.Num(); ++SectionIndex)
{
UMovieSceneSection* Section = ImportedSections[SectionIndex];
if (!Track->SupportsType(Section->GetClass()))
{
continue;
}
SectionIndicesImported.AddUnique(SectionIndex);
Track->Modify();
Section->ClearFlags(RF_Transient);
Section->Rename(nullptr, Track);
Track->AddSection(*Section);
if (Section->HasStartFrame())
{
FFrameNumber NewStartFrame = LocalTime + (Section->GetInclusiveStartFrame() - FirstFrame.GetValue());
Section->MoveSection(NewStartFrame - Section->GetInclusiveStartFrame());
}
if (Track->SupportsMultipleRows())
{
if (FTrackRowModel* TrackRowNode = TrackNode->CastThis<FTrackRowModel>())
{
Section->SetRowIndex(TrackRowNode->GetRowIndex());
}
}
NewSections.Add(Section);
}
// Fix up rows after sections are in place
if (Track->SupportsMultipleRows())
{
// If any newly created section overlaps the previous sections, put all the sections on the max available row
// Find the this section overlaps any previous sections,
int32 MaxAvailableRowIndex = -1;
for (UMovieSceneSection* Section : NewSections)
{
if (MovieSceneToolHelpers::OverlapsSection(Track, Section, NewSections))
{
int32 AvailableRowIndex = MovieSceneToolHelpers::FindAvailableRowIndex(Track, Section, NewSections);
MaxAvailableRowIndex = FMath::Max(AvailableRowIndex, MaxAvailableRowIndex);
}
}
if (MaxAvailableRowIndex != -1)
{
for (UMovieSceneSection* Section : NewSections)
{
Section->SetRowIndex(MaxAvailableRowIndex);
}
}
}
// Regenerate for pasting onto the next track
ImportedSections.Empty();
ImportedTracks.Empty();
FSequencer::ImportTracksFromText(TextToImport, ImportedTracks);
for (UMovieSceneCopyableTrack* CopyableTrack : ImportedTracks)
{
for (UMovieSceneSection* CopyableSection : CopyableTrack->Track->GetAllSections())
{
ImportedSections.Add(CopyableSection);
}
}
if (ImportedSections.Num() == 0)
{
FSequencer::ImportSectionsFromText(TextToImport, ImportedSections);
}
}
for (int32 SectionIndex = 0; SectionIndex < ImportedSections.Num(); ++SectionIndex)
{
if (!SectionIndicesImported.Contains(SectionIndex))
{
UE_LOG(LogSequencer, Display, TEXT("Could not paste section of type %s"), *ImportedSections[SectionIndex]->GetClass()->GetName());
}
}
if (SectionIndicesImported.Num() == 0)
{
FNotificationInfo Info(LOCTEXT("PasteSections_NothingPasted", "Can't paste section. No matching section types found."));
PasteErrors.Add(Info);
return false;
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
FSectionModelStorageExtension* SectionModelStorage = ViewModel->GetRootModel()->CastDynamic<FSectionModelStorageExtension>();
{
UE::MovieScene::FScopedSignedObjectModifyDefer ForceFlush(true);
check(SectionModelStorage);
}
EmptySelection();
Selection.SuspendBroadcast();
for (UMovieSceneSection* NewSection : NewSections)
{
TSharedPtr<FSectionModel> SectionModel = SectionModelStorage->FindModelForSection(NewSection);
if (ensure(SectionModel))
{
Selection.AddToSelection(SectionModel);
}
}
Selection.ResumeBroadcast();
ThrobSectionSelection();
return true;
}
class FTrackObjectTextFactory : public FCustomizableTextObjectFactory
{
public:
FTrackObjectTextFactory()
: FCustomizableTextObjectFactory(GWarn)
{
}
// FCustomizableTextObjectFactory implementation
virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override
{
if (InObjectClass->IsChildOf(UMovieSceneCopyableTrack::StaticClass()))
{
return true;
}
return false;
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
NewTracks.Add(Cast<UMovieSceneCopyableTrack>(NewObject));
}
public:
TArray<UMovieSceneCopyableTrack*> NewTracks;
};
class FSectionObjectTextFactory : public FCustomizableTextObjectFactory
{
public:
FSectionObjectTextFactory()
: FCustomizableTextObjectFactory(GWarn)
{
}
// FCustomizableTextObjectFactory implementation
virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override
{
if (InObjectClass->IsChildOf(UMovieSceneSection::StaticClass()))
{
return true;
}
return false;
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
NewSections.Add(Cast<UMovieSceneSection>(NewObject));
}
public:
TArray<UMovieSceneSection*> NewSections;
};
class FFolderObjectTextFactory : public FCustomizableTextObjectFactory
{
public:
FFolderObjectTextFactory()
: FCustomizableTextObjectFactory(GWarn)
{
}
// FCustomizableTextObjectFactory implementation
virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override
{
if (InObjectClass->IsChildOf(UMovieSceneFolder::StaticClass()))
{
return true;
}
return false;
}
virtual void ProcessConstructedObject(UObject* NewObject) override
{
check(NewObject);
NewFolders.Add(Cast<UMovieSceneFolder>(NewObject));
}
public:
TArray<UMovieSceneFolder*> NewFolders;
};
bool FSequencer::CanPaste(const FString& TextToImport)
{
FObjectBindingTextFactory ObjectBindingFactory(*this);
if (ObjectBindingFactory.CanCreateObjectsFromText(TextToImport))
{
return true;
}
FTrackObjectTextFactory TrackFactory;
if (TrackFactory.CanCreateObjectsFromText(TextToImport))
{
return true;
}
FSectionObjectTextFactory SectionFactory;
if (SectionFactory.CanCreateObjectsFromText(TextToImport))
{
return true;
}
FFolderObjectTextFactory FolderFactory;
if (FolderFactory.CanCreateObjectsFromText(TextToImport))
{
return true;
}
return false;
}
void FSequencer::ImportTracksFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneCopyableTrack*>& ImportedTracks)
{
UPackage* TempPackage = NewObject<UPackage>(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient);
TempPackage->AddToRoot();
// Turn the text buffer into objects
FTrackObjectTextFactory Factory;
Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport);
ImportedTracks = Factory.NewTracks;
// Remove the temp package from the root now that it has served its purpose
TempPackage->RemoveFromRoot();
}
void FSequencer::ObjectImplicitlyAdded(UObject* InObject) const
{
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
TrackEditors[i]->ObjectImplicitlyAdded(InObject);
}
}
void FSequencer::ObjectImplicitlyRemoved(UObject* InObject) const
{
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
TrackEditors[i]->ObjectImplicitlyRemoved(InObject);
}
}
void FSequencer::SetTrackFilterEnabled(const FText& InTrackFilterName, bool bEnabled)
{
SequencerWidget->SetTrackFilterEnabled(InTrackFilterName, bEnabled);
}
bool FSequencer::IsTrackFilterEnabled(const FText& InTrackFilterName) const
{
return SequencerWidget->IsTrackFilterEnabled(InTrackFilterName);
}
TArray<FText> FSequencer::GetTrackFilterNames() const
{
return SequencerWidget->GetTrackFilterNames();
}
void FSequencer::ImportSectionsFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneSection*>& ImportedSections)
{
UPackage* TempPackage = NewObject<UPackage>(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient);
TempPackage->AddToRoot();
// Turn the text buffer into objects
FSectionObjectTextFactory Factory;
Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport);
ImportedSections = Factory.NewSections;
// Remove the temp package from the root now that it has served its purpose
TempPackage->RemoveFromRoot();
}
void FSequencer::ImportFoldersFromText(const FString& TextToImport, /*out*/ TArray<UMovieSceneFolder*>& ImportedFolders)
{
UPackage* TempPackage = NewObject<UPackage>(nullptr, TEXT("/Engine/Sequencer/Editor/Transient"), RF_Transient);
TempPackage->AddToRoot();
// Turn the text buffer into objects
FFolderObjectTextFactory Factory;
Factory.ProcessBuffer(TempPackage, RF_Transactional, TextToImport);
ImportedFolders = Factory.NewFolders;
// Remove the temp package from the root now that it has served its purpose
TempPackage->RemoveFromRoot();
}
void FSequencer::ToggleNodeActive()
{
bool bIsActive = !IsNodeActive();
const FScopedTransaction Transaction( NSLOCTEXT("Sequencer", "ToggleNodeActive", "Toggle Node Active") );
for (auto OutlinerNode : Selection.GetSelectedOutlinerItems())
{
TSet<TWeakObjectPtr<UMovieSceneSection> > Sections;
SequencerHelpers::GetAllSections(OutlinerNode.Pin(), Sections);
for (auto Section : Sections)
{
Section->Modify();
Section->SetIsActive(bIsActive);
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
bool FSequencer::IsNodeActive() const
{
// Active if ONE is active, changed in 4.20
for (auto OutlinerNode : Selection.GetSelectedOutlinerItems())
{
TSet<TWeakObjectPtr<UMovieSceneSection> > Sections;
SequencerHelpers::GetAllSections(OutlinerNode.Pin(), Sections);
if (Sections.Num() > 0)
{
for (auto Section : Sections)
{
if (Section->IsActive())
{
return true;
}
}
return false;
}
}
return true;
}
void FSequencer::ToggleNodeLocked()
{
bool bIsLocked = !IsNodeLocked();
const FScopedTransaction Transaction( NSLOCTEXT("Sequencer", "ToggleNodeLocked", "Toggle Node Locked") );
for (auto OutlinerNode : Selection.GetSelectedOutlinerItems())
{
TSet<TWeakObjectPtr<UMovieSceneSection> > Sections;
SequencerHelpers::GetAllSections(OutlinerNode.Pin(), Sections);
for (auto Section : Sections)
{
Section->Modify();
Section->SetIsLocked(bIsLocked);
}
}
}
bool FSequencer::IsNodeLocked() const
{
// Locked only if all are locked
int NumSections = 0;
for (auto OutlinerNode : Selection.GetSelectedOutlinerItems())
{
TSet<TWeakObjectPtr<UMovieSceneSection> > Sections;
SequencerHelpers::GetAllSections(OutlinerNode.Pin(), Sections);
for (auto Section : Sections)
{
if (!Section->IsLocked())
{
return false;
}
++NumSections;
}
}
return NumSections > 0;
}
void FSequencer::GroupSelectedSections()
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction(LOCTEXT("GroupSelectedSections", "Group Selected Sections"));
TArray<UMovieSceneSection*> Sections;
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : Selection.GetSelectedSections())
{
UMovieSceneSection* Section = WeakSection.Get();
// We do not want to group sections that are infinite, as they should not be moveable
if (Section && (Section->HasStartFrame() || Section->HasEndFrame()))
{
Sections.Add(Section);
}
}
MovieScene->GroupSections(Sections);
}
bool FSequencer::CanGroupSelectedSections() const
{
int32 GroupableSections = 0;
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : Selection.GetSelectedSections())
{
UMovieSceneSection* Section = WeakSection.Get();
// We do not want to group sections that are infinite, as they should not be moveable
if (Section && (Section->HasStartFrame() || Section->HasEndFrame()))
{
if (++GroupableSections >= 2)
{
return true;
}
}
}
return false;
}
void FSequencer::UngroupSelectedSections()
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction(LOCTEXT("UngroupSelectedSections", "Ungroup Selected Sections"));
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : Selection.GetSelectedSections())
{
if (WeakSection.IsValid())
{
MovieScene->UngroupSection(*WeakSection.Get());
}
}
}
bool FSequencer::CanUngroupSelectedSections() const
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : Selection.GetSelectedSections())
{
if (WeakSection.IsValid() && MovieScene->IsSectionInGroup(*WeakSection.Get()))
{
return true;
}
}
return false;
}
void FSequencer::SaveSelectedNodesSpawnableState()
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction( LOCTEXT("SaveSpawnableState", "Save spawnable state") );
MovieScene->Modify();
TArray<FMovieSceneSpawnable*> Spawnables;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid());
if (Spawnable)
{
Spawnables.Add(Spawnable);
}
}
}
FScopedSlowTask SlowTask(Spawnables.Num(), LOCTEXT("SaveSpawnableStateProgress", "Saving selected spawnables"));
SlowTask.MakeDialog(true);
TArray<AActor*> PossessedActors;
for (FMovieSceneSpawnable* Spawnable : Spawnables)
{
SlowTask.EnterProgressFrame();
SpawnRegister->SaveDefaultSpawnableState(*Spawnable, ActiveTemplateIDs.Top(), *this);
if (GWarn->ReceivedUserCancel())
{
break;
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
void FSequencer::SetSelectedNodesSpawnableLevel(FName InLevelName)
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction( LOCTEXT("SetSpawnableLevel", "Set Spawnable Level") );
MovieScene->Modify();
TArray<FMovieSceneSpawnable*> Spawnables;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid());
if (Spawnable)
{
Spawnable->SetLevelName(InLevelName);
}
}
}
}
void FSequencer::ConvertToSpawnable(TSharedRef<UE::Sequencer::FObjectBindingModel> NodeToBeConverted)
{
using namespace UE::Sequencer;
if (GetFocusedMovieSceneSequence()->GetMovieScene()->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction( LOCTEXT("ConvertSelectedNodeSpawnable", "Convert Node to Spawnables") );
// Ensure we're in a non-possessed state
TGuardValue<bool> Guard(bUpdatingExternalSelection, true);
RestorePreAnimatedState();
GetFocusedMovieSceneSequence()->GetMovieScene()->Modify();
FMovieScenePossessable* Possessable = GetFocusedMovieSceneSequence()->GetMovieScene()->FindPossessable(NodeToBeConverted->GetObjectGuid());
if (Possessable)
{
ConvertToSpawnableInternal(Possessable->GetGuid());
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemsChanged );
}
}
TArray<FGuid> FSequencer::ConvertToSpawnable(FGuid Guid)
{
TArray< FMovieSceneSpawnable*> Spawnables = ConvertToSpawnableInternal(Guid);
TArray<FGuid> SpawnableGuids;
if (Spawnables.Num() > 0)
{
for (FMovieSceneSpawnable* Spawnable: Spawnables)
{
FGuid NewGuid = Spawnable->GetGuid();
SpawnableGuids.Add(NewGuid);
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
return SpawnableGuids;
}
void FSequencer::ConvertSelectedNodesToSpawnables()
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
// @todo sequencer: Undo doesn't seem to be working at all
const FScopedTransaction Transaction( LOCTEXT("ConvertSelectedNodesSpawnable", "Convert Selected Nodes to Spawnables") );
// Ensure we're in a non-possessed state
TGuardValue<bool> Guard(bUpdatingExternalSelection, true);
RestorePreAnimatedState();
MovieScene->Modify();
TArray<IObjectBindingExtension*> ObjectBindingNodes;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
// If we have a possessable for this node, and it has no parent, we can convert it to a spawnable
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBindingNode->GetObjectGuid());
if (Possessable && !Possessable->GetParent().IsValid())
{
ObjectBindingNodes.Add(ObjectBindingNode);
}
}
}
FScopedSlowTask SlowTask(ObjectBindingNodes.Num(), LOCTEXT("ConvertSpawnableProgress", "Converting Selected Possessable Nodes to Spawnables"));
SlowTask.MakeDialog(true);
TArray<AActor*> SpawnedActors;
for (IObjectBindingExtension* ObjectBindingNode : ObjectBindingNodes)
{
SlowTask.EnterProgressFrame();
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBindingNode->GetObjectGuid());
if (Possessable)
{
TArray<FMovieSceneSpawnable*> Spawnables = ConvertToSpawnableInternal(Possessable->GetGuid());
for (FMovieSceneSpawnable* Spawnable : Spawnables)
{
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(Spawnable->GetGuid(), ActiveTemplateIDs.Top()))
{
if (AActor* SpawnedActor = Cast<AActor>(WeakObject.Get()))
{
SpawnedActors.Add(SpawnedActor);
}
}
}
}
if (GWarn->ReceivedUserCancel())
{
break;
}
}
if (SpawnedActors.Num())
{
const bool bNotifySelectionChanged = true;
const bool bDeselectBSP = true;
const bool bWarnAboutTooManyActors = false;
const bool bSelectEvenIfHidden = false;
GEditor->GetSelectedActors()->Modify();
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
GEditor->SelectNone(bNotifySelectionChanged, bDeselectBSP, bWarnAboutTooManyActors);
for (auto SpawnedActor : SpawnedActors)
{
GEditor->SelectActor(SpawnedActor, true, bNotifySelectionChanged, bSelectEvenIfHidden);
}
GEditor->GetSelectedActors()->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
}
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemsChanged );
}
TArray<FGuid> FSequencer::ExpandMultiplePossessableBindings(FGuid PossessableGuid)
{
using namespace UE::Sequencer;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
TArray<FGuid> NewPossessableGuids;
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return TArray<FGuid>();
}
// Create a copy of the TArrayView of bound objects, as the underlying array will get destroyed
TArray<TWeakObjectPtr<>> FoundObjects;
for (TWeakObjectPtr<> BoundObject : FindBoundObjects(PossessableGuid, ActiveTemplateIDs.Top()))
{
FoundObjects.Insert(BoundObject,0);
}
if (FoundObjects.Num() < 2)
{
// If less than two objects, nothing to do, return the same Guid
NewPossessableGuids.Add(PossessableGuid);
return NewPossessableGuids;
}
Sequence->Modify();
MovieScene->Modify();
FMovieSceneBinding* PossessableBinding = (FMovieSceneBinding*)MovieScene->GetBindings().FindByPredicate([&](FMovieSceneBinding& Binding) { return Binding.GetObjectGuid() == PossessableGuid; });
// First gather the children
TArray<FGuid> ChildPossessableGuids;
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
if (Possessable.GetParent() == PossessableGuid)
{
ChildPossessableGuids.Add(Possessable.GetGuid());
}
}
TArray<UMovieSceneTrack* > Tracks = PossessableBinding->StealTracks(MovieScene);
// Remove binding to stop any children from claiming the old guid as their parent
if (MovieScene->RemovePossessable(PossessableGuid))
{
Sequence->UnbindPossessableObjects(PossessableGuid);
}
for (TWeakObjectPtr<> FoundObjectPtr : FoundObjects)
{
UObject* FoundObject = FoundObjectPtr.Get();
if (!FoundObject)
{
continue;
}
FoundObject->Modify();
UObject* BindingContext = GetPlaybackContext();
// Find this object's parent object, if it has one.
UObject* ParentObject = Sequence->GetParentObject(FoundObject);
if (ParentObject)
{
BindingContext = ParentObject;
}
// Create a new Possessable for this object
AActor* PossessedActor = Cast<AActor>(FoundObject);
const FGuid NewPossessableGuid = MovieScene->AddPossessable(PossessedActor != nullptr ? PossessedActor->GetActorLabel() : FoundObject->GetName(), FoundObject->GetClass());
FMovieScenePossessable* NewPossessable = MovieScene->FindPossessable(NewPossessableGuid);
if (NewPossessable)
{
FMovieSceneBinding* NewPossessableBinding = (FMovieSceneBinding*)MovieScene->GetBindings().FindByPredicate([&](FMovieSceneBinding& Binding) { return Binding.GetObjectGuid() == NewPossessableGuid; });
if (ParentObject)
{
FGuid ParentGuid = FindObjectId(*ParentObject, ActiveTemplateIDs.Top());
NewPossessable->SetParent(ParentGuid, MovieScene);
}
if (!NewPossessable->BindSpawnableObject(GetFocusedTemplateID(), FoundObject, this))
{
Sequence->BindPossessableObject(NewPossessableGuid, *FoundObject, BindingContext);
}
NewPossessableGuids.Add(NewPossessableGuid);
// Create copies of the tracks
for (UMovieSceneTrack* Track : Tracks)
{
UMovieSceneTrack* DuplicatedTrack = Cast<UMovieSceneTrack>(StaticDuplicateObject(Track, MovieScene));
NewPossessableBinding->AddTrack(*DuplicatedTrack, MovieScene);
}
}
}
// Finally, recurse in to any children
for (FGuid ChildPossessableGuid : ChildPossessableGuids)
{
ExpandMultiplePossessableBindings(ChildPossessableGuid);
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
return NewPossessableGuids;
}
TArray<FMovieSceneSpawnable*> FSequencer::ConvertToSpawnableInternal(FGuid PossessableGuid)
{
using namespace UE::Sequencer;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (MovieScene->IsReadOnly() || !Sequence->AllowsSpawnableObjects())
{
FSequencerUtilities::ShowReadOnlyError();
return TArray<FMovieSceneSpawnable*>();
}
TArrayView<TWeakObjectPtr<>> FoundObjects = FindBoundObjects(PossessableGuid, ActiveTemplateIDs.Top());
TArray<FMovieSceneSpawnable*> CreatedSpawnables;
if (FoundObjects.Num() == 0)
{
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(PossessableGuid);
UE_LOG(LogSequencer, Error, TEXT("Failed to convert %s to spawnable because there are no objects bound to it"), Possessable ? *Possessable->GetName() : TEXT(""));
}
else if (FoundObjects.Num() > 1)
{
// Expand to individual possessables for each bound object, then convert each one individually
TArray<FGuid> ExpandedPossessableGuids = ExpandMultiplePossessableBindings(PossessableGuid);
for (FGuid NewPossessableGuid : ExpandedPossessableGuids)
{
CreatedSpawnables.Append(ConvertToSpawnableInternal(NewPossessableGuid));
}
ForceEvaluate();
}
else
{
UObject* FoundObject = FoundObjects[0].Get();
if (!FoundObject)
{
return TArray<FMovieSceneSpawnable*>();
}
Sequence->Modify();
MovieScene->Modify();
// Locate the folder containing the original possessable
UMovieSceneFolder* ParentFolder;
for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders())
{
ParentFolder = Folder->FindFolderContaining(PossessableGuid);
if (ParentFolder != nullptr)
{
break;
}
}
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(AddSpawnable(*FoundObject));
if (Spawnable)
{
CreatedSpawnables.Add(Spawnable);
FGuid SpawnableGuid = Spawnable->GetGuid();
// Remap all the spawnable's tracks and child bindings onto the new possessable
MovieScene->MoveBindingContents(PossessableGuid, SpawnableGuid);
FMovieSceneBinding* PossessableBinding = (FMovieSceneBinding*)MovieScene->GetBindings().FindByPredicate([&](FMovieSceneBinding& Binding) { return Binding.GetObjectGuid() == PossessableGuid; });
check(PossessableBinding);
for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders())
{
if (ReplaceFolderBindingGUID(Folder, PossessableGuid, SpawnableGuid))
{
break;
}
}
int32 SortingOrder = PossessableBinding->GetSortingOrder();
if (MovieScene->RemovePossessable(PossessableGuid))
{
Sequence->UnbindPossessableObjects(PossessableGuid);
FMovieSceneBinding* SpawnableBinding = (FMovieSceneBinding*)MovieScene->GetBindings().FindByPredicate([&](FMovieSceneBinding& Binding) { return Binding.GetObjectGuid() == SpawnableGuid; });
check(SpawnableBinding);
SpawnableBinding->SetSortingOrder(SortingOrder);
}
TOptional<FTransformData> TransformData;
SpawnRegister->HandleConvertPossessableToSpawnable(FoundObject, *this, TransformData);
SpawnRegister->SetupDefaultsForSpawnable(nullptr, Spawnable->GetGuid(), TransformData, AsShared(), Settings);
FSequencerUtilities::UpdateBindingIDs(this, CompiledDataManager, PossessableGuid, Spawnable->GetGuid());
ForceEvaluate();
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
}
return CreatedSpawnables;
}
void FSequencer::ConvertToPossessable(TSharedRef<UE::Sequencer::FObjectBindingModel> NodeToBeConverted)
{
using namespace UE::Sequencer;
if (GetFocusedMovieSceneSequence()->GetMovieScene()->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction( LOCTEXT("ConvertSelectedNodePossessable", "Convert Node to Possessables") );
// Ensure we're in a non-possessed state
TGuardValue<bool> Guard(bUpdatingExternalSelection, true);
RestorePreAnimatedState();
GetFocusedMovieSceneSequence()->GetMovieScene()->Modify();
FMovieSceneSpawnable* Spawnable = GetFocusedMovieSceneSequence()->GetMovieScene()->FindSpawnable(NodeToBeConverted->GetObjectGuid());
if (Spawnable)
{
ConvertToPossessableInternal(Spawnable->GetGuid());
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
}
void FSequencer::ConvertSelectedNodesToPossessables()
{
using namespace UE::Sequencer;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
TArray<IObjectBindingExtension*> ObjectBindingNodes;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid());
if (Spawnable && SpawnRegister->CanConvertSpawnableToPossessable(*Spawnable))
{
ObjectBindingNodes.Add(ObjectBindingNode);
}
}
}
if (ObjectBindingNodes.Num() > 0)
{
const FScopedTransaction Transaction(LOCTEXT("ConvertSelectedNodesPossessable", "Convert Selected Nodes to Possessables"));
MovieScene->Modify();
FScopedSlowTask SlowTask(ObjectBindingNodes.Num(), LOCTEXT("ConvertPossessablesProgress", "Converting Selected Spawnable Nodes to Possessables"));
SlowTask.MakeDialog(true);
TArray<AActor*> PossessedActors;
for (IObjectBindingExtension* ObjectBindingNode : ObjectBindingNodes)
{
SlowTask.EnterProgressFrame();
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid());
if (Spawnable)
{
FMovieScenePossessable* Possessable = ConvertToPossessableInternal(Spawnable->GetGuid());
ForceEvaluate();
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(Possessable->GetGuid(), ActiveTemplateIDs.Top()))
{
if (AActor* PossessedActor = Cast<AActor>(WeakObject.Get()))
{
PossessedActors.Add(PossessedActor);
}
}
}
if (GWarn->ReceivedUserCancel())
{
break;
}
}
if (PossessedActors.Num())
{
const bool bNotifySelectionChanged = true;
const bool bDeselectBSP = true;
const bool bWarnAboutTooManyActors = false;
const bool bSelectEvenIfHidden = false;
GEditor->GetSelectedActors()->Modify();
GEditor->GetSelectedActors()->BeginBatchSelectOperation();
GEditor->SelectNone(bNotifySelectionChanged, bDeselectBSP, bWarnAboutTooManyActors);
for (auto PossessedActor : PossessedActors)
{
GEditor->SelectActor(PossessedActor, true, bNotifySelectionChanged, bSelectEvenIfHidden);
}
GEditor->GetSelectedActors()->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
}
}
FMovieScenePossessable* FSequencer::ConvertToPossessableInternal(FGuid SpawnableGuid)
{
using namespace UE::Sequencer;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return nullptr;
}
// Find the object in the environment
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(SpawnableGuid);
if (!Spawnable || !Spawnable->GetObjectTemplate())
{
return nullptr;
}
AActor* SpawnableActorTemplate = Cast<AActor>(Spawnable->GetObjectTemplate());
if (!SpawnableActorTemplate)
{
return nullptr;
}
TMap<TWeakObjectPtr<AActor>, FTransform> AttachedChildTransforms;
FTransform DefaultTransform = SpawnableActorTemplate->GetActorTransform();
for (TWeakObjectPtr<> RuntimeObject : FindBoundObjects(SpawnableGuid, ActiveTemplateIDs.Top()) )
{
// Prefer the transform at the current time over the spawnable actor template's transform because that's most likely 0.
// This makes it so that the object will return to the current position on restore state.
AActor* Actor = Cast<AActor>(RuntimeObject.Get());
if (Actor)
{
if (Actor->GetRootComponent())
{
DefaultTransform = Actor->GetRootComponent()->GetRelativeTransform();
}
// Removing a parent will compensate the children at their world transform. We don't want that since we'll be replacing that parent right away.
// To negate that, we store the relative transform of these children and reset it after the parent is replaced with the new possessable.
TArray<AActor*> AttachedActors;
Actor->GetAttachedActors(AttachedActors);
for (AActor* ChildActor : AttachedActors)
{
if (ChildActor && ChildActor->GetRootComponent())
{
// Only do this for child actors that Sequencer is controlling
FGuid ExistingID;
for (FMovieSceneSequenceID SequenceID : ActiveTemplateIDs)
{
ExistingID = FindObjectId(*ChildActor, SequenceID);
if (ExistingID.IsValid())
{
AttachedChildTransforms.Add(ChildActor);
AttachedChildTransforms[ChildActor] = ChildActor->GetRootComponent()->GetRelativeTransform();
break;
}
}
}
}
break;
}
}
Sequence->Modify();
MovieScene->Modify();
// Delete the spawn track
UMovieSceneSpawnTrack* SpawnTrack = Cast<UMovieSceneSpawnTrack>(MovieScene->FindTrack(UMovieSceneSpawnTrack::StaticClass(), SpawnableGuid, NAME_None));
if (SpawnTrack)
{
MovieScene->RemoveTrack(*SpawnTrack);
}
FActorSpawnParameters SpawnInfo;
SpawnInfo.bDeferConstruction = true;
SpawnInfo.Template = SpawnableActorTemplate;
UWorld* PlaybackContext = Cast<UWorld>(GetPlaybackContext());
AActor* PossessedActor = PlaybackContext->SpawnActor(Spawnable->GetObjectTemplate()->GetClass(), &DefaultTransform, SpawnInfo);
if (!PossessedActor)
{
return nullptr;
}
PossessedActor->SetActorLabel(Spawnable->GetName());
const bool bIsDefaultTransform = true;
PossessedActor->FinishSpawning(DefaultTransform, bIsDefaultTransform);
// The transform needs to be set again for deferred construction and dynamic root components. Until the fix for: UE-67537
PossessedActor->SetActorTransform(DefaultTransform);
const FGuid NewPossessableGuid = CreateBinding(*PossessedActor, PossessedActor->GetActorLabel());
const FGuid OldSpawnableGuid = Spawnable->GetGuid();
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(NewPossessableGuid);
if (Possessable)
{
// Remap all the spawnable's tracks and child bindings onto the new possessable
MovieScene->MoveBindingContents(OldSpawnableGuid, NewPossessableGuid);
FMovieSceneBinding* SpawnableBinding = MovieScene->FindBinding(OldSpawnableGuid);
check(SpawnableBinding);
for (UMovieSceneFolder* Folder : MovieScene->GetRootFolders())
{
if (ReplaceFolderBindingGUID(Folder, Spawnable->GetGuid(), Possessable->GetGuid()))
{
break;
}
}
int32 SortingOrder = SpawnableBinding->GetSortingOrder();
// Remove the spawnable and all it's sub tracks
if (MovieScene->RemoveSpawnable(OldSpawnableGuid))
{
SpawnRegister->DestroySpawnedObject(OldSpawnableGuid, ActiveTemplateIDs.Top(), *this);
FMovieSceneBinding* PossessableBinding = MovieScene->FindBinding(NewPossessableGuid);
check(PossessableBinding);
PossessableBinding->SetSortingOrder(SortingOrder);
}
static const FName SequencerActorTag(TEXT("SequencerActor"));
PossessedActor->Tags.Remove(SequencerActorTag);
FSequencerUtilities::UpdateBindingIDs(this, CompiledDataManager, OldSpawnableGuid, NewPossessableGuid);
GEditor->SelectActor(PossessedActor, false, true);
for (TPair<TWeakObjectPtr<AActor>, FTransform> AttachedChildTransform : AttachedChildTransforms)
{
if (AActor* AttachedChild = AttachedChildTransform.Key.Get())
{
if (AttachedChild->GetRootComponent())
{
AttachedChild->GetRootComponent()->SetRelativeTransform(AttachedChildTransform.Value);
}
}
}
ForceEvaluate();
}
return Possessable;
}
void FSequencer::OnLoadRecordedData()
{
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSceneSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSceneSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
return;
}
TArray<FString> OpenFilenames;
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
bool bOpen = false;
if (DesktopPlatform)
{
FString FileTypeDescription = TEXT("");
FString DialogTitle = TEXT("Open Recorded Sequencer Data");
FString InOpenDirectory = FPaths::ProjectSavedDir();
bOpen = DesktopPlatform->OpenFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
DialogTitle,
InOpenDirectory,
TEXT(""),
FileTypeDescription,
EFileDialogFlags::None,
OpenFilenames
);
}
if (!bOpen || !OpenFilenames.Num())
{
return;
}
IModularFeatures& ModularFeatures = IModularFeatures::Get();
if (ModularFeatures.IsModularFeatureAvailable(ISerializedRecorder::ModularFeatureName))
{
ISerializedRecorder* Recorder = &IModularFeatures::Get().GetModularFeature<ISerializedRecorder>(ISerializedRecorder::ModularFeatureName);
if (Recorder)
{
FScopedTransaction AddFolderTransaction(NSLOCTEXT("Sequencer", "LoadRecordedData_Transaction", "Load Recorded Data"));
auto OnReadComplete = [this]()
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
}; //callback
UWorld* PlaybackContext = Cast<UWorld>(GetPlaybackContext());
for (const FString& FileName : OpenFilenames)
{
Recorder->LoadRecordedSequencerFile(FocusedMovieSceneSequence, PlaybackContext, FileName, OnReadComplete);
}
}
}
}
bool FSequencer::ReplaceFolderBindingGUID(UMovieSceneFolder* Folder, FGuid Original, FGuid Converted)
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return true;
}
for (FGuid ChildGuid : Folder->GetChildObjectBindings())
{
if (ChildGuid == Original)
{
Folder->AddChildObjectBinding(Converted);
Folder->RemoveChildObjectBinding(Original);
return true;
}
}
for (UMovieSceneFolder* ChildFolder : Folder->GetChildFolders())
{
if (ReplaceFolderBindingGUID(ChildFolder, Original, Converted))
{
return true;
}
}
return false;
}
void FSequencer::OnAddFolder()
{
using namespace UE::Sequencer;
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction AddFolderTransaction( NSLOCTEXT("Sequencer", "AddFolder_Transaction", "Add Folder") );
// Check if a folder, or child of a folder is currently selected.
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
TArray<FName> ExistingFolderNames;
// If there is a folder selected the existing folder names are the sibling folders.
if ( SelectedParentFolders.Num() == 1 )
{
for ( UMovieSceneFolder* SiblingFolder : SelectedParentFolders[0]->GetChildFolders() )
{
ExistingFolderNames.Add( SiblingFolder->GetFolderName() );
}
}
// Otherwise use the root folders.
else
{
for ( UMovieSceneFolder* MovieSceneFolder : FocusedMovieScene->GetRootFolders() )
{
ExistingFolderNames.Add( MovieSceneFolder->GetFolderName() );
}
}
FName UniqueName = FSequencerUtilities::GetUniqueName(FName("New Folder"), ExistingFolderNames);
UMovieSceneFolder* NewFolder = NewObject<UMovieSceneFolder>( FocusedMovieScene, NAME_None, RF_Transactional );
NewFolder->SetFolderName( UniqueName );
// The folder's name is used as it's key in the path system.
NewNodePath += UniqueName.ToString();
if ( SelectedParentFolders.Num() == 1 )
{
SelectedParentFolders[0]->AddChildFolder( NewFolder );
}
else
{
FocusedMovieScene->Modify();
FocusedMovieScene->AddRootFolder( NewFolder );
}
Selection.Empty();
// We can't add the newly created folder to the selection set as the nodes for it don't actually exist yet.
// However, we can calculate the resulting path that the node will end up at and add that to the selection
// set, which will cause the newly created node to be selected when the selection is restored post-refresh.
SequencerWidget->AddAdditionalPathToSelectionSet(NewNodePath);
SequencerWidget->RequestRenameNode(NewNodePath);
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemAdded );
}
void FSequencer::OnAddTrack(const TWeakObjectPtr<UMovieSceneTrack>& InTrack, const FGuid& ObjectBinding)
{
if (!ensureAlwaysMsgf(InTrack.IsValid(), TEXT("Attempted to add a null UMovieSceneTrack to Sequencer. This should never happen.")))
{
return;
}
FString NewNodePath;
// If they specified an object binding it's being added to, we don't add it to a folder since we can't have it existing
// as a children of two places at once.
if(!GetFocusedMovieSceneSequence()->GetMovieScene()->FindBinding(ObjectBinding))
{
TArray<UMovieSceneFolder*> SelectedParentFolders;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
if (SelectedParentFolders.Num() == 1)
{
SelectedParentFolders[0]->Modify();
SelectedParentFolders[0]->AddChildMasterTrack(InTrack.Get());
}
}
// We can't add the newly created folder to the selection set as the nodes for it don't actually exist yet.
// However, we can calculate the resulting path that the node will end up at and add that to the selection
// set, which will cause the newly created node to be selected when the selection is restored post-refresh.
NewNodePath += InTrack->GetFName().ToString();
SequencerWidget->AddAdditionalPathToSelectionSet(NewNodePath);
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
EmptySelection();
if (InTrack->GetAllSections().Num() > 0)
{
SelectSection(InTrack->GetAllSections()[0]);
}
ThrobSectionSelection();
}
void FSequencer::CalculateSelectedFolderAndPath(TArray<UMovieSceneFolder*>& OutSelectedParentFolders, FString& OutNewNodePath)
{
using namespace UE::Sequencer;
// Check if a folder, or child of a folder is currently selected.
if (Selection.GetSelectedOutlinerItems().Num() > 0)
{
for (TWeakPtr<FViewModel> SelectedNode : Selection.GetSelectedOutlinerItems())
{
TSharedPtr<FViewModel> CurrentNode = SelectedNode.Pin();
while (CurrentNode.IsValid() && !CurrentNode->IsA<FFolderModel>())
{
CurrentNode = CurrentNode->GetParent();
}
if (CurrentNode.IsValid())
{
OutSelectedParentFolders.Add(CurrentNode->CastThisChecked<FFolderModel>()->GetFolder());
// The first valid folder we find will be used to put the new folder into, so it's the node that we
// want to know the path from.
if (OutNewNodePath.Len() == 0)
{
// Add an extra delimiter (".") as we know that the new folder will be appended onto the end of this.
OutNewNodePath = FString::Printf(TEXT("%s."), *IOutlinerExtension::GetPathName(CurrentNode));
// Make sure this folder is expanded too so that adding objects to hidden folders become visible.
CurrentNode->CastThisChecked<IOutlinerExtension>()->SetExpansion(true);
}
}
}
}
}
void FSequencer::TogglePlay()
{
OnPlay(true);
}
void FSequencer::JumpToStart()
{
OnJumpToStart();
}
void FSequencer::JumpToEnd()
{
OnJumpToEnd();
}
void FSequencer::RestorePlaybackSpeed()
{
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
CurrentSpeedIndex = PlaybackSpeeds.Find(1.f);
check(CurrentSpeedIndex != INDEX_NONE);
PlaybackSpeed = PlaybackSpeeds[CurrentSpeedIndex];
if (PlaybackState != EMovieScenePlayerStatus::Playing)
{
OnPlayForward(false);
}
}
void FSequencer::ShuttleForward()
{
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
float CurrentSpeed = GetPlaybackSpeed();
int32 Sign = 0;
if(PlaybackState == EMovieScenePlayerStatus::Playing)
{
// if we are at positive speed, increase the positive speed
if (CurrentSpeed > 0)
{
CurrentSpeedIndex = FMath::Min(PlaybackSpeeds.Num() - 1, ++CurrentSpeedIndex);
Sign = 1;
}
else if (CurrentSpeed < 0)
{
// if we are at the negative slowest speed, turn to positive slowest speed
if (CurrentSpeedIndex == 0)
{
Sign = 1;
}
// otherwise, just reduce negative speed
else
{
CurrentSpeedIndex = FMath::Max(0, --CurrentSpeedIndex);
Sign = -1;
}
}
}
else
{
Sign = 1;
CurrentSpeedIndex = PlaybackSpeeds.Find(1);
}
PlaybackSpeed = PlaybackSpeeds[CurrentSpeedIndex] * Sign;
if (PlaybackState != EMovieScenePlayerStatus::Playing)
{
OnPlayForward(false);
}
}
void FSequencer::ShuttleBackward()
{
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
float CurrentSpeed = GetPlaybackSpeed();
int32 Sign = 0;
if(PlaybackState == EMovieScenePlayerStatus::Playing)
{
if (CurrentSpeed > 0)
{
// if we are at the positive slowest speed, turn to negative slowest speed
if (CurrentSpeedIndex == 0)
{
Sign = -1;
}
// otherwise, just reduce positive speed
else
{
CurrentSpeedIndex = FMath::Max(0, --CurrentSpeedIndex);
Sign = 1;
}
}
// if we are at negative speed, increase the negative speed
else if (CurrentSpeed < 0)
{
CurrentSpeedIndex = FMath::Min(PlaybackSpeeds.Num() - 1, ++CurrentSpeedIndex);
Sign = -1;
}
}
else
{
Sign = -1;
CurrentSpeedIndex = PlaybackSpeeds.Find(1);
}
PlaybackSpeed = PlaybackSpeeds[CurrentSpeedIndex] * Sign;
if (PlaybackState != EMovieScenePlayerStatus::Playing)
{
OnPlayBackward(false);
}
}
void FSequencer::SnapToClosestPlaybackSpeed()
{
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
float CurrentSpeed = GetPlaybackSpeed();
float Delta = TNumericLimits<float>::Max();
int32 NewSpeedIndex = INDEX_NONE;
for (int32 Idx = 0; Idx < PlaybackSpeeds.Num(); Idx++)
{
float NewDelta = FMath::Abs(CurrentSpeed - PlaybackSpeeds[Idx]);
if (NewDelta < Delta)
{
Delta = NewDelta;
NewSpeedIndex = Idx;
}
}
if (NewSpeedIndex != INDEX_NONE)
{
PlaybackSpeed = PlaybackSpeeds[NewSpeedIndex];
}
}
void FSequencer::Pause()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
// When stopping a sequence, we always evaluate a non-empty range if possible. This ensures accurate paused motion blur effects.
if (Settings->GetIsSnapEnabled())
{
FQualifiedFrameTime LocalTime = GetLocalTime();
FFrameRate FocusedDisplayRate = GetFocusedDisplayRate();
// Snap to the focused play rate
FFrameTime RootPosition = FFrameRate::Snap(LocalTime.Time, LocalTime.Rate, FocusedDisplayRate) * RootToLocalTransform.InverseFromWarp(RootToLocalLoopCounter);
// Convert the root position from tick resolution time base (the output rate), to the play position input rate
FFrameTime InputPosition = ConvertFrameTime(RootPosition, PlayPosition.GetOutputRate(), PlayPosition.GetInputRate());
EvaluateInternal(PlayPosition.PlayTo(InputPosition));
}
else
{
// Update on stop (cleans up things like sounds that are playing)
FMovieSceneEvaluationRange Range = PlayPosition.GetLastRange().Get(PlayPosition.GetCurrentPositionAsRange());
EvaluateInternal(Range);
}
// reset the speed to 1. We have to update the speed index as well.
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
CurrentSpeedIndex = PlaybackSpeeds.Find(1.f);
check(CurrentSpeedIndex != INDEX_NONE);
PlaybackSpeed = PlaybackSpeeds[CurrentSpeedIndex];
OnStopDelegate.Broadcast();
}
void FSequencer::StepForward()
{
OnStepForward();
}
void FSequencer::StepBackward()
{
OnStepBackward();
}
void FSequencer::JumpForward()
{
OnStepForward(Settings->GetJumpFrameIncrement());
}
void FSequencer::JumpBackward()
{
OnStepBackward(Settings->GetJumpFrameIncrement());
}
void FSequencer::StepToNextKey()
{
SequencerWidget->StepToNextKey();
}
void FSequencer::StepToPreviousKey()
{
SequencerWidget->StepToPreviousKey();
}
void FSequencer::StepToNextCameraKey()
{
SequencerWidget->StepToNextCameraKey();
}
void FSequencer::StepToPreviousCameraKey()
{
SequencerWidget->StepToPreviousCameraKey();
}
void FSequencer::StepToNextShot()
{
if (ActiveTemplateIDs.Num() < 2)
{
UMovieSceneSection* TargetShotSection = FindNextOrPreviousShot(GetFocusedMovieSceneSequence(), GetLocalTime().Time.FloorToFrame(), true);
if (TargetShotSection)
{
SetLocalTime(TargetShotSection->GetRange().GetLowerBoundValue(), ESnapTimeMode::STM_None);
}
return;
}
FMovieSceneSequenceID OuterSequenceID = ActiveTemplateIDs[ActiveTemplateIDs.Num() - 2];
UMovieSceneSequence* Sequence = RootTemplateInstance.GetSequence(OuterSequenceID);
FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(RootSequence.Get());
const FMovieSceneSequenceHierarchy& Hierarchy = CompiledDataManager->GetHierarchyChecked(DataID);
const FMovieSceneSubSequenceData* SubData = Hierarchy.FindSubData(GetFocusedTemplateID());
if (!SubData)
{
return;
}
FFrameTime CurrentTime = SubSequenceRange.GetLowerBoundValue() * SubData->OuterToInnerTransform.InverseFromWarp(RootToLocalLoopCounter);
UMovieSceneSubSection* NextShot = Cast<UMovieSceneSubSection>(FindNextOrPreviousShot(Sequence, CurrentTime.FloorToFrame(), true));
if (!NextShot)
{
return;
}
SequencerWidget->PopBreadcrumb();
PopToSequenceInstance(ActiveTemplateIDs[ActiveTemplateIDs.Num()-2]);
FocusSequenceInstance(*NextShot);
SetLocalTime(SubSequenceRange.GetLowerBoundValue(), ESnapTimeMode::STM_None);
}
void FSequencer::StepToPreviousShot()
{
if (ActiveTemplateIDs.Num() < 2)
{
UMovieSceneSection* TargetShotSection = FindNextOrPreviousShot(GetFocusedMovieSceneSequence(), GetLocalTime().Time.FloorToFrame(), false);
if (TargetShotSection)
{
SetLocalTime(TargetShotSection->GetRange().GetLowerBoundValue(), ESnapTimeMode::STM_None);
}
return;
}
FMovieSceneSequenceID OuterSequenceID = ActiveTemplateIDs[ActiveTemplateIDs.Num() - 2];
UMovieSceneSequence* Sequence = RootTemplateInstance.GetSequence(OuterSequenceID);
FMovieSceneCompiledDataID DataID = CompiledDataManager->Compile(RootSequence.Get());
const FMovieSceneSequenceHierarchy& Hierarchy = CompiledDataManager->GetHierarchyChecked(DataID);
const FMovieSceneSubSequenceData* SubData = Hierarchy.FindSubData(GetFocusedTemplateID());
if (!SubData)
{
return;
}
FFrameTime CurrentTime = SubSequenceRange.GetLowerBoundValue() * SubData->OuterToInnerTransform.InverseFromWarp(RootToLocalLoopCounter);
UMovieSceneSubSection* PreviousShot = Cast<UMovieSceneSubSection>(FindNextOrPreviousShot(Sequence, CurrentTime.FloorToFrame(), false));
if (!PreviousShot)
{
return;
}
SequencerWidget->PopBreadcrumb();
PopToSequenceInstance(ActiveTemplateIDs[ActiveTemplateIDs.Num()-2]);
FocusSequenceInstance(*PreviousShot);
SetLocalTime(SubSequenceRange.GetLowerBoundValue(), ESnapTimeMode::STM_None);
}
FReply FSequencer::NavigateForward()
{
TArray<FMovieSceneSequenceID> TemplateIDForwardStackCopy = TemplateIDForwardStack;
TArray<FMovieSceneSequenceID> TemplateIDBackwardStackCopy = TemplateIDBackwardStack;
TemplateIDBackwardStackCopy.Push(ActiveTemplateIDs.Top());
FMovieSceneSequenceID SequenceID = TemplateIDForwardStackCopy.Pop();
if (SequenceID == MovieSceneSequenceID::Root)
{
PopToSequenceInstance(SequenceID);
}
else if (UMovieSceneSubSection* SubSection = FindSubSection(SequenceID))
{
FocusSequenceInstance(*SubSection);
}
TemplateIDForwardStack = TemplateIDForwardStackCopy;
TemplateIDBackwardStack = TemplateIDBackwardStackCopy;
SequencerWidget->UpdateBreadcrumbs();
return FReply::Handled();
}
FReply FSequencer::NavigateBackward()
{
TArray<FMovieSceneSequenceID> TemplateIDForwardStackCopy = TemplateIDForwardStack;
TArray<FMovieSceneSequenceID> TemplateIDBackwardStackCopy = TemplateIDBackwardStack;
TemplateIDForwardStackCopy.Push(ActiveTemplateIDs.Top());
FMovieSceneSequenceID SequenceID = TemplateIDBackwardStackCopy.Pop();
if (SequenceID == MovieSceneSequenceID::Root)
{
PopToSequenceInstance(SequenceID);
}
else if (UMovieSceneSubSection* SubSection = FindSubSection(SequenceID))
{
FocusSequenceInstance(*SubSection);
}
TemplateIDForwardStack = TemplateIDForwardStackCopy;
TemplateIDBackwardStack = TemplateIDBackwardStackCopy;
SequencerWidget->UpdateBreadcrumbs();
return FReply::Handled();
}
bool FSequencer::CanNavigateForward() const
{
return TemplateIDForwardStack.Num() > 0;
}
bool FSequencer::CanNavigateBackward() const
{
return TemplateIDBackwardStack.Num() > 0;
}
FText FSequencer::GetNavigateForwardTooltip() const
{
if (TemplateIDForwardStack.Num() > 0)
{
FMovieSceneSequenceID SequenceID = TemplateIDForwardStack.Last();
if (SequenceID == MovieSceneSequenceID::Root)
{
if (GetRootMovieSceneSequence())
{
return FText::Format(LOCTEXT("NavigateForwardTooltipFmt", "Forward to {0}"), GetRootMovieSceneSequence()->GetDisplayName());
}
}
else if (UMovieSceneSubSection* SubSection = FindSubSection(SequenceID))
{
if (SubSection->GetSequence())
{
return FText::Format(LOCTEXT("NavigateForwardTooltipFmt", "Forward to {0}"), SubSection->GetSequence()->GetDisplayName());
}
}
}
return FText::GetEmpty();
}
FText FSequencer::GetNavigateBackwardTooltip() const
{
if (TemplateIDBackwardStack.Num() > 0)
{
FMovieSceneSequenceID SequenceID = TemplateIDBackwardStack.Last();
if (SequenceID == MovieSceneSequenceID::Root)
{
if (GetRootMovieSceneSequence())
{
return FText::Format( LOCTEXT("NavigateBackwardTooltipFmt", "Back to {0}"), GetRootMovieSceneSequence()->GetDisplayName());
}
}
else if (UMovieSceneSubSection* SubSection = FindSubSection(SequenceID))
{
if (SubSection->GetSequence())
{
return FText::Format(LOCTEXT("NavigateBackwardTooltipFmt", "Back to {0}"), SubSection->GetSequence()->GetDisplayName());
}
}
}
return FText::GetEmpty();
}
void FSequencer::SortAllNodesAndDescendants()
{
FScopedTransaction SortAllNodesTransaction(NSLOCTEXT("Sequencer", "SortAllNodes_Transaction", "Sort Tracks"));
NodeTree->SortAllNodesAndDescendants();
}
void FSequencer::ToggleExpandCollapseNodes()
{
using namespace UE::Sequencer;
SequencerWidget->GetTreeView()->ToggleExpandCollapseNodes(ETreeRecursion::NonRecursive);
}
void FSequencer::ToggleExpandCollapseNodesAndDescendants()
{
using namespace UE::Sequencer;
SequencerWidget->GetTreeView()->ToggleExpandCollapseNodes(ETreeRecursion::Recursive);
}
void FSequencer::ExpandAllNodes()
{
using namespace UE::Sequencer;
const bool bExpandAll = true;
const bool bCollapseAll = false;
SequencerWidget->GetTreeView()->ToggleExpandCollapseNodes(ETreeRecursion::Recursive, bExpandAll, bCollapseAll);
}
void FSequencer::CollapseAllNodes()
{
using namespace UE::Sequencer;
const bool bExpandAll = false;
const bool bCollapseAll = true;
SequencerWidget->GetTreeView()->ToggleExpandCollapseNodes(ETreeRecursion::Recursive, bExpandAll, bCollapseAll);
}
void FSequencer::ResetFilters()
{
SequencerWidget->ResetFilters();
}
void FSequencer::AddSelectedActors()
{
USelection* ActorSelection = GEditor->GetSelectedActors();
TArray<TWeakObjectPtr<AActor> > SelectedActors;
for (FSelectionIterator Iter(*ActorSelection); Iter; ++Iter)
{
AActor* Actor = Cast<AActor>(*Iter);
if (Actor)
{
SelectedActors.Add(Actor);
}
}
AddActors(SelectedActors);
}
void FSequencer::SetKey()
{
if (Selection.GetSelectedOutlinerItems().Num() > 0)
{
using namespace UE::Sequencer;
FScopedTransaction SetKeyTransaction( NSLOCTEXT("Sequencer", "SetKey_Transaction", "Set Key") );
const FFrameNumber KeyTime = GetLocalTime().Time.FrameNumber;
FAddKeyOperation::FromNodes(Selection.GetSelectedOutlinerItems()).Commit(KeyTime, *this);
}
}
bool FSequencer::CanSetKeyTime() const
{
return Selection.GetSelectedKeys().Num() > 0;
}
void FSequencer::SetKeyTime()
{
TArray<FSequencerSelectedKey> SelectedKeysArray = Selection.GetSelectedKeys().Array();
FFrameNumber KeyTime = 0;
for ( const FSequencerSelectedKey& Key : SelectedKeysArray )
{
if (Key.IsValid())
{
KeyTime = Key.WeakChannel.Pin()->GetKeyArea()->GetKeyTime(Key.KeyHandle);
break;
}
}
// Create a popup showing the existing time value and let the user set a new one.
GenericTextEntryModeless(NSLOCTEXT("Sequencer.Popups", "SetKeyTimePopup", "New Time"), FText::FromString(GetNumericTypeInterface()->ToString(KeyTime.Value)),
FOnTextCommitted::CreateSP(this, &FSequencer::OnSetKeyTimeTextCommitted)
);
}
void FSequencer::OnSetKeyTimeTextCommitted(const FText& InText, ETextCommit::Type CommitInfo)
{
bool bAnythingChanged = false;
CloseEntryPopupMenu();
if (CommitInfo == ETextCommit::OnEnter)
{
TOptional<double> NewFrameTime = GetNumericTypeInterface()->FromString(InText.ToString(), 0);
if (!NewFrameTime.IsSet())
return;
FFrameNumber NewFrame = FFrameNumber((int32)NewFrameTime.GetValue());
FScopedTransaction SetKeyTimeTransaction(NSLOCTEXT("Sequencer", "SetKeyTime_Transaction", "Set Key Time"));
TArray<FSequencerSelectedKey> SelectedKeysArray = Selection.GetSelectedKeys().Array();
for ( const FSequencerSelectedKey& Key : SelectedKeysArray )
{
if (Key.IsValid())
{
if (Key.Section->TryModify())
{
Key.WeakChannel.Pin()->GetKeyArea()->SetKeyTime(Key.KeyHandle, NewFrame);
bAnythingChanged = true;
Key.Section->ExpandToFrame(NewFrame);
}
}
}
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
}
bool FSequencer::CanRekey() const
{
return Selection.GetSelectedKeys().Num() > 0;
}
void FSequencer::Rekey()
{
bool bAnythingChanged = false;
FQualifiedFrameTime CurrentTime = GetLocalTime();
FScopedTransaction RekeyTransaction(NSLOCTEXT("Sequencer", "Rekey_Transaction", "Rekey"));
TArray<FSequencerSelectedKey> SelectedKeysArray = Selection.GetSelectedKeys().Array();
for ( const FSequencerSelectedKey& Key : SelectedKeysArray )
{
if (Key.IsValid())
{
if (Key.Section->TryModify())
{
Key.WeakChannel.Pin()->GetKeyArea()->SetKeyTime(Key.KeyHandle, CurrentTime.Time.FrameNumber);
bAnythingChanged = true;
Key.Section->ExpandToFrame(CurrentTime.Time.FrameNumber);
}
}
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
}
TSet<FFrameNumber> FSequencer::GetVerticalFrames() const
{
TSet<FFrameNumber> VerticalFrames;
auto AddVerticalFrames = [](auto &InVerticalFrames, auto InTrack)
{
for (UMovieSceneSection* Section : InTrack->GetAllSections())
{
if (Section->GetRange().HasLowerBound())
{
InVerticalFrames.Add(Section->GetRange().GetLowerBoundValue());
}
if (Section->GetRange().HasUpperBound())
{
InVerticalFrames.Add(Section->GetRange().GetUpperBoundValue());
}
}
};
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (FocusedMovieSequence != nullptr)
{
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (FocusedMovieScene != nullptr)
{
for (UMovieSceneTrack* MasterTrack : FocusedMovieScene->GetMasterTracks())
{
if (MasterTrack && MasterTrack->DisplayOptions.bShowVerticalFrames)
{
AddVerticalFrames(VerticalFrames, MasterTrack);
}
}
if (UMovieSceneTrack* CameraCutTrack = FocusedMovieScene->GetCameraCutTrack())
{
if (CameraCutTrack->DisplayOptions.bShowVerticalFrames)
{
AddVerticalFrames(VerticalFrames, CameraCutTrack);
}
}
}
}
return VerticalFrames;
}
TArray<FMovieSceneMarkedFrame> FSequencer::GetMarkedFrames() const
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (FocusedMovieSequence != nullptr)
{
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (FocusedMovieScene != nullptr)
{
return FocusedMovieScene->GetMarkedFrames();
}
}
return TArray<FMovieSceneMarkedFrame>();
}
TArray<FMovieSceneMarkedFrame> FSequencer::GetGlobalMarkedFrames() const
{
return GlobalMarkedFramesCache;
}
void FSequencer::UpdateGlobalMarkedFramesCache()
{
GlobalMarkedFramesCache.Empty();
TArray<uint32> LoopCounts = RootToLocalLoopCounter.WarpCounts;
if (LoopCounts.Num() > 0)
{
LoopCounts.Last() += LocalLoopIndexOffsetDuringScrubbing;
}
FSequencerMarkedFrameHelper::FindGlobalMarkedFrames(*this, LoopCounts, GlobalMarkedFramesCache);
bGlobalMarkedFramesCached = true;
}
void FSequencer::ClearGlobalMarkedFrames()
{
FSequencerMarkedFrameHelper::ClearGlobalMarkedFrames(*this);
bGlobalMarkedFramesCached = false;
}
void FSequencer::ToggleMarkAtPlayPosition()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FFrameNumber TickFrameNumber = GetLocalTime().Time.FloorToFrame();
int32 MarkedFrameIndex = FocusedMovieScene->FindMarkedFrameByFrameNumber(TickFrameNumber);
if (MarkedFrameIndex != INDEX_NONE)
{
FScopedTransaction DeleteMarkedFrameTransaction(LOCTEXT("DeleteMarkedFrames_Transaction", "Delete Marked Frame"));
FocusedMovieScene->Modify();
FocusedMovieScene->DeleteMarkedFrame(MarkedFrameIndex);
}
else
{
FScopedTransaction AddMarkedFrameTransaction(LOCTEXT("AddMarkedFrame_Transaction", "Add Marked Frame"));
FocusedMovieScene->Modify();
FocusedMovieScene->AddMarkedFrame(FMovieSceneMarkedFrame(TickFrameNumber));
}
}
void FSequencer::SetMarkedFrame(int32 InMarkIndex, FFrameNumber InFrameNumber)
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FocusedMovieScene->Modify();
FocusedMovieScene->SetMarkedFrame(InMarkIndex, InFrameNumber);
}
void FSequencer::AddMarkedFrame(FFrameNumber FrameNumber)
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction AddMarkedFrameTransaction(LOCTEXT("AddMarkedFrame_Transaction", "Add Marked Frame"));
FocusedMovieScene->Modify();
FocusedMovieScene->AddMarkedFrame(FMovieSceneMarkedFrame(FrameNumber));
}
void FSequencer::DeleteMarkedFrame(int32 InMarkIndex)
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
if (InMarkIndex != INDEX_NONE)
{
FScopedTransaction DeleteMarkedFrameTransaction(LOCTEXT("DeleteMarkedFrame_Transaction", "Delete Marked Frame"));
FocusedMovieScene->Modify();
FocusedMovieScene->DeleteMarkedFrame(InMarkIndex);
}
}
void FSequencer::DeleteAllMarkedFrames()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction DeleteAllMarkedFramesTransaction(LOCTEXT("DeleteAllMarkedFrames_Transaction", "Delete All Marked Frames"));
FocusedMovieScene->Modify();
FocusedMovieScene->DeleteMarkedFrames();
}
void FSequencer::StepToNextMark()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
const bool bForwards = true;
int32 MarkedIndex = FocusedMovieScene->FindNextMarkedFrame(GetLocalTime().Time.FloorToFrame(), bForwards);
if (MarkedIndex != INDEX_NONE)
{
AutoScrubToTime(FocusedMovieScene->GetMarkedFrames()[MarkedIndex].FrameNumber.Value);
}
}
void FSequencer::StepToPreviousMark()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
const bool bForwards = false;
int32 MarkedIndex = FocusedMovieScene->FindNextMarkedFrame(GetLocalTime().Time.FloorToFrame(), bForwards);
if (MarkedIndex != INDEX_NONE)
{
AutoScrubToTime(FocusedMovieScene->GetMarkedFrames()[MarkedIndex].FrameNumber.Value);
}
}
void GatherTracksAndObjectsToCopy(TSharedRef<UE::Sequencer::FViewModel> Node, TArray<TSharedPtr<UE::Sequencer::FViewModel>>& TracksToCopy, TArray<TSharedPtr<UE::Sequencer::FObjectBindingModel>>& ObjectsToCopy, TArray<UMovieSceneFolder*>& FoldersToCopy)
{
using namespace UE::Sequencer;
if (ITrackExtension* TrackNode = Node->CastThis<ITrackExtension>())
{
if (!TracksToCopy.Contains(Node))
{
TracksToCopy.Add(Node);
}
}
else if (TSharedPtr<FObjectBindingModel> ObjectNode = Node->CastThisShared<FObjectBindingModel>())
{
if (!ObjectsToCopy.Contains(ObjectNode))
{
ObjectsToCopy.Add(ObjectNode);
}
}
else if (FFolderModel* FolderNode = Node->CastThis<FFolderModel>())
{
FoldersToCopy.Add(FolderNode->GetFolder());
for (TSharedPtr<FViewModel> ChildNode : FolderNode->GetChildren())
{
GatherTracksAndObjectsToCopy(ChildNode.ToSharedRef(), TracksToCopy, ObjectsToCopy, FoldersToCopy);
}
}
}
void FSequencer::CopySelection()
{
using namespace UE::Sequencer;
if (Selection.GetSelectedKeys().Num() != 0)
{
CopySelectedKeys();
}
else if (Selection.GetSelectedSections().Num() != 0)
{
CopySelectedSections();
}
else
{
TArray<TSharedPtr<FViewModel>> TracksToCopy;
TArray<TSharedPtr<FObjectBindingModel>> ObjectsToCopy;
TArray<UMovieSceneFolder*> FoldersToCopy;
TArray<TSharedRef<FViewModel>> SelectedNodes;
Selection.GetNodesWithSelectedKeysOrSections(SelectedNodes);
if (SelectedNodes.Num() == 0)
{
Selection.GetSelectedOutlinerItems(SelectedNodes);
}
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
GatherTracksAndObjectsToCopy(Node, TracksToCopy, ObjectsToCopy, FoldersToCopy);
}
// Make a empty clipboard if the stack is empty
if (GClipboardStack.Num() == 0)
{
TSharedRef<FMovieSceneClipboard> NullClipboard = MakeShareable(new FMovieSceneClipboard());
GClipboardStack.Push(NullClipboard);
}
FString ObjectsExportedText;
FString TracksExportedText;
FString FoldersExportedText;
if (ObjectsToCopy.Num())
{
CopySelectedObjects(ObjectsToCopy, FoldersToCopy, ObjectsExportedText);
}
if (TracksToCopy.Num())
{
CopySelectedTracks(TracksToCopy, FoldersToCopy, TracksExportedText);
}
if (FoldersToCopy.Num())
{
CopySelectedFolders(FoldersToCopy, FoldersExportedText);
}
FString ExportedText;
ExportedText += ObjectsExportedText;
ExportedText += TracksExportedText;
ExportedText += FoldersExportedText;
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}
}
void FSequencer::CutSelection()
{
if (Selection.GetSelectedKeys().Num() != 0)
{
CutSelectedKeys();
}
else if (Selection.GetSelectedSections().Num() != 0)
{
CutSelectedSections();
}
else
{
FScopedTransaction CutSelectionTransaction(LOCTEXT("CutSelection_Transaction", "Cut Selection"));
CopySelection();
DeleteSelectedItems();
}
}
void FSequencer::DuplicateSelection()
{
FScopedTransaction DuplicateSelectionTransaction(LOCTEXT("DuplicateSelection_Transaction", "Duplicate Selection"));
const bool bClearSelection = true;
if (Selection.GetSelectedKeys().Num() != 0)
{
CopySelection();
DoPaste(bClearSelection);
// Shift duplicated keys by one display rate frame as an overlapping key isn't useful
// Offset by a visible amount
FFrameNumber FrameOffset = FFrameNumber((int32)GetDisplayRateDeltaFrameCount());
TArray<FSequencerSelectedKey> NewSelection;
for (const FSequencerSelectedKey& Key : Selection.GetSelectedKeys())
{
if (Key.IsValid())
{
TSharedPtr<IKeyArea> KeyArea = Key.WeakChannel.Pin()->GetKeyArea();
FKeyHandle KeyHandle = Key.KeyHandle;
FKeyHandle NewKeyHandle = KeyArea->DuplicateKey(KeyHandle);
KeyArea->SetKeyTime(NewKeyHandle, KeyArea->GetKeyTime(KeyHandle) + FrameOffset);
NewSelection.Add(FSequencerSelectedKey(*KeyArea->GetOwningSection(), Key.WeakChannel, NewKeyHandle));
}
}
Selection.SuspendBroadcast();
Selection.EmptySelectedKeys();
for (const FSequencerSelectedKey& Key : NewSelection)
{
Selection.AddToSelection(Key);
}
Selection.ResumeBroadcast();
Selection.GetOnKeySelectionChanged().Broadcast();
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
else if (Selection.GetSelectedSections().Num() != 0)
{
CopySelection();
DoPaste(bClearSelection);
}
else
{
CopySelection();
DoPaste(bClearSelection);
SynchronizeSequencerSelectionWithExternalSelection();
}
}
void FSequencer::CopySelectedKeys()
{
using namespace UE::Sequencer;
TOptional<FFrameNumber> CopyRelativeTo;
// Copy relative to the current key hotspot, if applicable
if (TSharedPtr<FKeyHotspot> KeyHotspot = HotspotCast<FKeyHotspot>(GetViewModel()->GetTrackArea()->GetHotspot()))
{
CopyRelativeTo = KeyHotspot->GetTime();
}
FMovieSceneClipboardBuilder Builder;
// Map selected keys to their key areas
TMap<TSharedPtr<FChannelModel>, TArray<FKeyHandle>> KeyAreaMap;
for (const FSequencerSelectedKey& Key : Selection.GetSelectedKeys())
{
if (Key.IsValid())
{
KeyAreaMap.FindOrAdd(Key.WeakChannel.Pin()).Add(Key.KeyHandle);
}
}
// Serialize each key area to the clipboard
for (const TPair<TSharedPtr<FChannelModel>, TArray<FKeyHandle>>& Pair : KeyAreaMap)
{
Pair.Key->GetKeyArea()->CopyKeys(Builder, Pair.Value);
}
TSharedRef<FMovieSceneClipboard> Clipboard = MakeShareable( new FMovieSceneClipboard(Builder.Commit(CopyRelativeTo)) );
Clipboard->GetEnvironment().TickResolution = GetFocusedTickResolution();
if (Clipboard->GetKeyTrackGroups().Num())
{
GClipboardStack.Push(Clipboard);
if (GClipboardStack.Num() > 10)
{
GClipboardStack.RemoveAt(0, 1);
}
}
// Make sure to clear the clipboard for the sections/tracks/bindings
FPlatformApplicationMisc::ClipboardCopy(TEXT(""));
}
void FSequencer::CutSelectedKeys()
{
FScopedTransaction CutSelectedKeysTransaction(LOCTEXT("CutSelectedKeys_Transaction", "Cut Selected keys"));
CopySelectedKeys();
DeleteSelectedKeys();
}
void FSequencer::CopySelectedSections()
{
TArray<UObject*> SelectedSections;
for (TWeakObjectPtr<UMovieSceneSection> SelectedSectionPtr : Selection.GetSelectedSections())
{
if (SelectedSectionPtr.IsValid())
{
SelectedSections.Add(SelectedSectionPtr.Get());
}
}
FString ExportedText;
FSequencer::ExportObjectsToText(SelectedSections, /*out*/ ExportedText);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
// Make sure to clear the clipboard for the keys
GClipboardStack.Empty();
}
void FSequencer::CutSelectedSections()
{
FScopedTransaction CutSelectedSectionsTransaction(LOCTEXT("CutSelectedSections_Transaction", "Cut Selected sections"));
CopySelectedSections();
DeleteSections(Selection.GetSelectedSections());
}
const TArray<TSharedPtr<FMovieSceneClipboard>>& FSequencer::GetClipboardStack() const
{
return GClipboardStack;
}
void FSequencer::OnClipboardUsed(TSharedPtr<FMovieSceneClipboard> Clipboard)
{
Clipboard->GetEnvironment().DateTime = FDateTime::UtcNow();
// Last entry in the stack should be the most up-to-date
GClipboardStack.Sort([](const TSharedPtr<FMovieSceneClipboard>& A, const TSharedPtr<FMovieSceneClipboard>& B){
return A->GetEnvironment().DateTime < B->GetEnvironment().DateTime;
});
}
void FSequencer::CreateCamera()
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
if (!World)
{
return;
}
const FScopedTransaction Transaction(NSLOCTEXT("Sequencer", "CreateCameraHere", "Create Camera Here"));
const bool bCreateAsSpawnable = Settings->GetCreateSpawnableCameras() && GetFocusedMovieSceneSequence()->AllowsSpawnableObjects();
FActorSpawnParameters SpawnParams;
if (bCreateAsSpawnable)
{
// Don't bother transacting this object if we're creating a spawnable since it's temporary
SpawnParams.ObjectFlags &= ~RF_Transactional;
}
// Set new camera to match viewport
ACineCameraActor* NewCamera = World->SpawnActor<ACineCameraActor>(SpawnParams);
if (!NewCamera)
{
return;
}
FGuid CameraGuid;
FMovieSceneSpawnable* Spawnable = nullptr;
if (bCreateAsSpawnable)
{
FString NewName = MovieSceneHelpers::MakeUniqueSpawnableName(FocusedMovieScene, FName::NameToDisplayString(ACineCameraActor::StaticClass()->GetFName().ToString(), false));
CameraGuid = MakeNewSpawnable(*NewCamera);
Spawnable = FocusedMovieScene->FindSpawnable(CameraGuid);
if (ensure(Spawnable))
{
Spawnable->SetName(NewName);
}
// Destroy the old actor
World->EditorDestroyActor(NewCamera, false);
for (TWeakObjectPtr<UObject>& Object : FindBoundObjects(CameraGuid, ActiveTemplateIDs.Top()))
{
NewCamera = Cast<ACineCameraActor>(Object.Get());
if (NewCamera)
{
break;
}
}
ensure(NewCamera);
NewCamera->SetActorLabel(NewName, false);
}
else
{
CameraGuid = CreateBinding(*NewCamera, NewCamera->GetActorLabel());
}
if (!CameraGuid.IsValid())
{
return;
}
NewCamera->SetActorLocation( GCurrentLevelEditingViewportClient->GetViewLocation(), false );
NewCamera->SetActorRotation( GCurrentLevelEditingViewportClient->GetViewRotation() );
//pNewCamera->CameraComponent->FieldOfView = ViewportClient->ViewFOV; //@todo set the focal length from this field of view
OnActorAddedToSequencerEvent.Broadcast(NewCamera, CameraGuid);
NewCameraAdded(NewCamera, CameraGuid);
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
}
void FSequencer::NewCameraAdded(ACameraActor* NewCamera, FGuid CameraGuid)
{
if (OnCameraAddedToSequencer().IsBound() && !OnCameraAddedToSequencer().Execute(NewCamera, CameraGuid))
{
return;
}
MovieSceneToolHelpers::LockCameraActorToViewport(SharedThis(this), NewCamera);
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if (Sequence->IsTrackSupported(UMovieSceneCameraCutTrack::StaticClass()) == ETrackSupport::Supported)
{
MovieSceneToolHelpers::CreateCameraCutSectionForCamera(Sequence->GetMovieScene(), CameraGuid, GetLocalTime().Time.FloorToFrame());
}
}
void FSequencer::FixPossessableObjectClassInternal(UMovieSceneSequence* Sequence, FMovieSceneSequenceIDRef SequenceID)
{
UMovieScene* MovieScene = Sequence->GetMovieScene();
MovieScene->Modify();
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
TOptional<UClass*> CommonBaseClass;
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(Possessable.GetGuid(), SequenceID))
{
if (WeakObject.IsValid())
{
if (!CommonBaseClass.IsSet())
{
CommonBaseClass = WeakObject->GetClass();
}
else
{
CommonBaseClass = UClass::FindCommonBase(WeakObject->GetClass(), CommonBaseClass.GetValue());
}
}
}
if (CommonBaseClass.IsSet() && CommonBaseClass.GetValue() != Possessable.GetPossessedObjectClass())
{
UE_LOG(LogSequencer, Display, TEXT("Updated possessed object class in: %s for: %s, from: %s, to: %s"), *GetNameSafe(Sequence), *Possessable.GetName(), *GetNameSafe(Possessable.GetPossessedObjectClass()), *GetNameSafe(CommonBaseClass.GetValue()));
Possessable.SetPossessedObjectClass(CommonBaseClass.GetValue());
}
}
}
void FSequencer::FixPossessableObjectClass()
{
FScopedTransaction FixPossessableObjectClassTransaction( NSLOCTEXT( "Sequencer", "FixPossessableObjectClass", "Fix Possessable Object Class" ) );
FMovieSceneRootEvaluationTemplateInstance& RootTemplate = GetEvaluationTemplate();
UMovieSceneSequence* Sequence = RootTemplate.GetSequence(MovieSceneSequenceID::Root);
FixPossessableObjectClassInternal(Sequence, MovieSceneSequenceID::Root);
const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(RootTemplateInstance.GetCompiledDataID());
if (Hierarchy)
{
FMovieSceneEvaluationTreeRangeIterator Iter = Hierarchy->GetTree().IterateFromTime(PlayPosition.GetCurrentPosition().FrameNumber);
for (const FMovieSceneSubSequenceTreeEntry& Entry : Hierarchy->GetTree().GetAllData(Iter.Node()))
{
UMovieSceneSequence* SubSequence = Hierarchy->FindSubSequence(Entry.SequenceID);
if (SubSequence)
{
FixPossessableObjectClassInternal(SubSequence, Entry.SequenceID);
}
}
}
}
void FSequencer::RebindPossessableReferences()
{
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
UMovieScene* FocusedMovieScene = FocusedSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction Transaction(LOCTEXT("RebindAllPossessables", "Rebind Possessable References"));
FocusedSequence->Modify();
TMap<FGuid, TArray<UObject*, TInlineAllocator<1>>> AllObjects;
UObject* PlaybackContext = PlaybackContextAttribute.Get(nullptr);
for (int32 Index = 0; Index < FocusedMovieScene->GetPossessableCount(); Index++)
{
const FMovieScenePossessable& Possessable = FocusedMovieScene->GetPossessable(Index);
TArray<UObject*, TInlineAllocator<1>>& References = AllObjects.FindOrAdd(Possessable.GetGuid());
FocusedSequence->LocateBoundObjects(Possessable.GetGuid(), PlaybackContext, References);
}
for (auto& Pair : AllObjects)
{
// Only rebind things if they exist
if (Pair.Value.Num() > 0)
{
FocusedSequence->UnbindPossessableObjects(Pair.Key);
for (UObject* Object : Pair.Value)
{
FocusedSequence->BindPossessableObject(Pair.Key, *Object, PlaybackContext);
}
}
}
}
void FSequencer::ImportFBX()
{
using namespace UE::Sequencer;
TMap<FGuid, FString> ObjectBindingNameMap;
TParentFirstChildIterator<IObjectBindingExtension> ObjectBindingIt = NodeTree->GetRootNode()->GetDescendantsOfType<IObjectBindingExtension>();
// Only visit the first object binding in a given sub-hierarchy so we get top-level object bindings.
// This is done by skipping the branch on each iteration
for ( ; ObjectBindingIt; ++ObjectBindingIt)
{
FGuid ObjectBinding = ObjectBindingIt->GetObjectGuid();
ObjectBindingNameMap.Add(ObjectBinding, (*ObjectBindingIt).AsModel()->CastThisChecked<IOutlinerExtension>()->GetLabel().ToString());
ObjectBindingIt.IgnoreCurrentChildren();
}
MovieSceneToolHelpers::ImportFBXWithDialog(GetFocusedMovieSceneSequence(), *this, ObjectBindingNameMap, TOptional<bool>());
}
void FSequencer::ImportFBXOntoSelectedNodes()
{
using namespace UE::Sequencer;
// The object binding and names to match when importing from fbx
TMap<FGuid, FString> ObjectBindingNameMap;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (FObjectBindingModel* ObjectBindingNode = ICastable::CastWeakPtr<FObjectBindingModel>(Node))
{
FGuid ObjectBinding = ObjectBindingNode->GetObjectGuid();
ObjectBindingNameMap.Add(ObjectBinding, ObjectBindingNode->GetLabel().ToString());
}
}
MovieSceneToolHelpers::ImportFBXWithDialog(GetFocusedMovieSceneSequence(), *this, ObjectBindingNameMap, TOptional<bool>(false));
}
void FSequencer::ExportFBX()
{
using namespace UE::Sequencer;
TArray<UExporter*> Exporters;
TArray<FString> SaveFilenames;
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
bool bExportFileNamePicked = false;
if ( DesktopPlatform != NULL )
{
FString FileTypes = "FBX document|*.fbx";
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
for (TObjectIterator<UClass> It; It; ++It)
{
if (!It->IsChildOf(UExporter::StaticClass()) || It->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists))
{
continue;
}
UExporter* Default = It->GetDefaultObject<UExporter>();
if (!Default->SupportsObject(Sequence))
{
continue;
}
for (int32 i = 0; i < Default->FormatExtension.Num(); ++i)
{
const FString& FormatExtension = Default->FormatExtension[i];
const FString& FormatDescription = Default->FormatDescription[i];
if (FileTypes.Len() > 0)
{
FileTypes += TEXT("|");
}
FileTypes += FormatDescription;
FileTypes += TEXT("|*.");
FileTypes += FormatExtension;
}
Exporters.Add(Default);
}
bExportFileNamePicked = DesktopPlatform->SaveFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
LOCTEXT( "ExportLevelSequence", "Export Level Sequence" ).ToString(),
*( FEditorDirectories::Get().GetLastDirectory( ELastDirectory::FBX ) ),
TEXT( "" ),
*FileTypes,
EFileDialogFlags::None,
SaveFilenames );
}
if ( bExportFileNamePicked )
{
FString ExportFilename = SaveFilenames[0];
FEditorDirectories::Get().SetLastDirectory( ELastDirectory::FBX, FPaths::GetPath( ExportFilename ) ); // Save path as default for next time.
// Make sure external selection is up to date since export could happen on tracks that have been right clicked but not have their underlying bound objects selected yet since that happens on mouse up.
SynchronizeExternalSelectionWithSequencerSelection();
// Select selected nodes if there are selected nodes
TArray<FGuid> Bindings;
TArray<UMovieSceneTrack*> MasterTracks;
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
Bindings.Add(ObjectBindingNode->GetObjectGuid());
TSet<TSharedRef<FViewModel> > DescendantNodes;
SequencerHelpers::GetDescendantNodes(Node.Pin().ToSharedRef(), DescendantNodes);
for (TSharedRef<FViewModel> DescendantNode : DescendantNodes)
{
if (!Selection.IsSelected(DescendantNode) && DescendantNode->IsA<IObjectBindingExtension>())
{
IObjectBindingExtension* DescendantObjectBindingNode = DescendantNode->CastThisChecked<IObjectBindingExtension>();
Bindings.Add(DescendantObjectBindingNode->GetObjectGuid());
}
}
}
else if (ITrackExtension* TrackNode = ICastable::CastWeakPtr<ITrackExtension>(Node))
{
UMovieSceneTrack* Track = TrackNode->GetTrack();
if (Track && MovieScene->IsAMasterTrack(*Track))
{
MasterTracks.Add(Track);
}
}
}
FString FileExtension = FPaths::GetExtension(ExportFilename);
if (FileExtension == TEXT("fbx"))
{
ExportFBXInternal(ExportFilename, Bindings, (Bindings.Num() + MasterTracks.Num()) > 0 ? MasterTracks : MovieScene->GetMasterTracks());
}
else
{
for (UExporter* Exporter : Exporters)
{
if (Exporter->FormatExtension.Contains(FileExtension))
{
USequencerExportTask* ExportTask = NewObject<USequencerExportTask>();
TStrongObjectPtr<USequencerExportTask> ExportTaskGuard(ExportTask);
ExportTask->Object = GetFocusedMovieSceneSequence();
ExportTask->Exporter = nullptr;
ExportTask->Filename = ExportFilename;
ExportTask->bSelected = false;
ExportTask->bReplaceIdentical = true;
ExportTask->bPrompt = false;
ExportTask->bUseFileArchive = false;
ExportTask->bWriteEmptyFiles = false;
ExportTask->bAutomated = false;
ExportTask->Exporter = NewObject<UExporter>(GetTransientPackage(), Exporter->GetClass());
ExportTask->SequencerContext = GetPlaybackContext();
UExporter::RunAssetExportTask(ExportTask);
ExportTask->Object = nullptr;
ExportTask->Exporter = nullptr;
ExportTask->SequencerContext = nullptr;
break;
}
}
}
}
}
void FSequencer::ExportFBXInternal(const FString& ExportFilename, const TArray<FGuid>& Bindings, const TArray<UMovieSceneTrack*>& MasterTracks)
{
{
UnFbx::FFbxExporter* Exporter = UnFbx::FFbxExporter::GetInstance();
//Show the fbx export dialog options
bool ExportCancel = false;
bool ExportAll = false;
Exporter->FillExportOptions(false, true, ExportFilename, ExportCancel, ExportAll);
if (!ExportCancel)
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
UWorld* World = Cast<UWorld>(GetPlaybackContext());
FMovieSceneSequenceIDRef Template = GetFocusedTemplateID();
UnFbx::FFbxExporter::FLevelSequenceNodeNameAdapter NodeNameAdapter(MovieScene, this, Template);
{
FSpawnableRestoreState SpawnableRestoreState(MovieScene);
if (SpawnableRestoreState.bWasChanged)
{
// Evaluate at the beginning of the subscene time to ensure that spawnables are created before export
SetLocalTimeDirectly(UE::MovieScene::DiscreteInclusiveLower(GetTimeBounds()));
}
if (MovieSceneToolHelpers::ExportFBX(World, MovieScene, this, Bindings, MasterTracks, NodeNameAdapter, Template, ExportFilename, RootToLocalTransform))
{
FNotificationInfo Info(NSLOCTEXT("Sequencer", "ExportFBXSucceeded", "FBX Export Succeeded."));
Info.Hyperlink = FSimpleDelegate::CreateStatic([](FString InFilename) { FPlatformProcess::ExploreFolder(*InFilename); }, ExportFilename);
Info.HyperlinkText = FText::FromString(ExportFilename);
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Success);
}
else
{
FNotificationInfo Info(NSLOCTEXT("Sequencer", "ExportFBXFailed", "FBX Export Failed."));
Info.ExpireDuration = 5.0f;
FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(SNotificationItem::CS_Fail);
}
}
ForceEvaluate();
}
}
}
void FSequencer::ExportToCameraAnim()
{
using namespace UE::Sequencer;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (FObjectBindingModel* ObjectBindingNode = ICastable::CastWeakPtr<FObjectBindingModel>(Node))
{
FGuid Guid = ObjectBindingNode->GetObjectGuid();
MovieSceneToolHelpers::ExportToCameraAnim(GetFocusedMovieSceneSequence()->GetMovieScene(), Guid);
}
}
}
void FSequencer::GenericTextEntryModeless(const FText& DialogText, const FText& DefaultText, FOnTextCommitted OnTextComitted)
{
TSharedRef<STextEntryPopup> TextEntryPopup =
SNew(STextEntryPopup)
.Label(DialogText)
.DefaultText(DefaultText)
.OnTextCommitted(OnTextComitted)
.ClearKeyboardFocusOnCommit(false)
.SelectAllTextWhenFocused(true)
.MaxWidth(1024.0f);
EntryPopupMenu = FSlateApplication::Get().PushMenu(
ToolkitHost.Pin()->GetParentWidget(),
FWidgetPath(),
TextEntryPopup,
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
);
}
void FSequencer::CloseEntryPopupMenu()
{
if (EntryPopupMenu.IsValid())
{
EntryPopupMenu.Pin()->Dismiss();
}
}
void FSequencer::TrimSection(bool bTrimLeft)
{
FScopedTransaction TrimSectionTransaction( NSLOCTEXT("Sequencer", "TrimSection_Transaction", "Trim Section") );
MovieSceneToolHelpers::TrimSection(Selection.GetSelectedSections(), GetLocalTime(), bTrimLeft, Settings->GetDeleteKeysWhenTrimming());
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
void FSequencer::TrimOrExtendSection(bool bTrimOrExtendLeft)
{
using namespace UE::Sequencer;
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = FocusedMovieSceneSequence ? FocusedMovieSceneSequence->GetMovieScene() : nullptr;
if (!MovieScene)
{
return;
}
FScopedTransaction TrimOrExtendSectionTransaction( NSLOCTEXT("Sequencer", "TrimOrExtendSection_Transaction", "Trim or Extend Section") );
if (Selection.GetSelectedOutlinerItems().Num() > 0)
{
TArray<TSharedRef<FViewModel>> SelectedNodes;
GetSelection().GetSelectedOutlinerItems(SelectedNodes);
for (const TSharedRef<FViewModel>& Node : SelectedNodes)
{
if (ITrackExtension* TrackNode = Node->CastThis<ITrackExtension>())
{
UMovieSceneTrack* Track = TrackNode->GetTrack();
if (Track)
{
MovieSceneToolHelpers::TrimOrExtendSection(Track, Node->IsA<FTrackRowModel>() ? TrackNode->GetRowIndex() : TOptional<int32>(), GetLocalTime(), bTrimOrExtendLeft, Settings->GetDeleteKeysWhenTrimming());
}
}
else if (IObjectBindingExtension* ObjectBindingNode = Node->CastThis<IObjectBindingExtension>())
{
const FMovieSceneBinding* Binding = MovieScene->FindBinding(ObjectBindingNode->GetObjectGuid());
if (Binding)
{
for (UMovieSceneTrack* Track : Binding->GetTracks())
{
MovieSceneToolHelpers::TrimOrExtendSection(Track, TOptional<int32>(), GetLocalTime(), bTrimOrExtendLeft, Settings->GetDeleteKeysWhenTrimming());
}
}
}
}
}
else
{
for (UMovieSceneTrack* Track : MovieScene->GetMasterTracks())
{
MovieSceneToolHelpers::TrimOrExtendSection(Track, TOptional<int32>(), GetLocalTime(), bTrimOrExtendLeft, Settings->GetDeleteKeysWhenTrimming());
}
for (const FMovieSceneBinding& Binding : MovieScene->GetBindings())
{
for (UMovieSceneTrack* Track : Binding.GetTracks())
{
MovieSceneToolHelpers::TrimOrExtendSection(Track, TOptional<int32>(), GetLocalTime(), bTrimOrExtendLeft, Settings->GetDeleteKeysWhenTrimming());
}
}
}
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
}
void FSequencer::SplitSection()
{
FScopedTransaction SplitSectionTransaction( NSLOCTEXT("Sequencer", "SplitSection_Transaction", "Split Section") );
MovieSceneToolHelpers::SplitSection(Selection.GetSelectedSections(), GetLocalTime(), Settings->GetDeleteKeysWhenTrimming());
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::RefreshAllImmediately );
}
void FSequencer::BindCommands()
{
using namespace UE::Sequencer;
const FSequencerCommands& Commands = FSequencerCommands::Get();
SequencerCommandBindings->MapAction(
Commands.TogglePlayViewport,
FExecuteAction::CreateSP(this, &FSequencer::TogglePlay));
SequencerCommandBindings->MapAction(
Commands.StepToNextKey,
FExecuteAction::CreateSP( this, &FSequencer::StepToNextKey) );
SequencerCommandBindings->MapAction(
Commands.StepToPreviousKey,
FExecuteAction::CreateSP( this, &FSequencer::StepToPreviousKey ) );
SequencerCommandBindings->MapAction(
Commands.StepForwardViewport,
FExecuteAction::CreateSP(this, &FSequencer::StepForward),
EUIActionRepeatMode::RepeatEnabled);
SequencerCommandBindings->MapAction(
Commands.StepBackwardViewport,
FExecuteAction::CreateSP(this, &FSequencer::StepBackward),
EUIActionRepeatMode::RepeatEnabled);
SequencerCommandBindings->MapAction(
Commands.StepToNextCameraKey,
FExecuteAction::CreateSP( this, &FSequencer::StepToNextCameraKey ) );
SequencerCommandBindings->MapAction(
Commands.StepToPreviousCameraKey,
FExecuteAction::CreateSP( this, &FSequencer::StepToPreviousCameraKey ) );
SequencerCommandBindings->MapAction(
Commands.SortAllNodesAndDescendants,
FExecuteAction::CreateSP(this, &FSequencer::SortAllNodesAndDescendants));
SequencerCommandBindings->MapAction(
Commands.ToggleExpandCollapseNodes,
FExecuteAction::CreateSP(this, &FSequencer::ToggleExpandCollapseNodes));
SequencerCommandBindings->MapAction(
Commands.ToggleExpandCollapseNodesAndDescendants,
FExecuteAction::CreateSP(this, &FSequencer::ToggleExpandCollapseNodesAndDescendants));
SequencerCommandBindings->MapAction(
Commands.ExpandAllNodes,
FExecuteAction::CreateSP(this, &FSequencer::ExpandAllNodes));
SequencerCommandBindings->MapAction(
Commands.CollapseAllNodes,
FExecuteAction::CreateSP(this, &FSequencer::CollapseAllNodes));
SequencerCommandBindings->MapAction(
Commands.ResetFilters,
FExecuteAction::CreateSP(this, &FSequencer::ResetFilters));
SequencerCommandBindings->MapAction(
Commands.AddActorsToSequencer,
FExecuteAction::CreateSP( this, &FSequencer::AddSelectedActors));
SequencerCommandBindings->MapAction(
Commands.SetKey,
FExecuteAction::CreateSP( this, &FSequencer::SetKey ) );
SequencerCommandBindings->MapAction(
Commands.TranslateLeft,
FExecuteAction::CreateSP( this, &FSequencer::TranslateSelectedKeysAndSections, true) );
SequencerCommandBindings->MapAction(
Commands.TranslateRight,
FExecuteAction::CreateSP( this, &FSequencer::TranslateSelectedKeysAndSections, false) );
auto CanTrimSection = [this]{
for (auto Section : Selection.GetSelectedSections())
{
if (Section.IsValid() && Section->IsTimeWithinSection(GetLocalTime().Time.FrameNumber))
{
return true;
}
}
return false;
};
SequencerCommandBindings->MapAction(
Commands.TrimSectionLeft,
FExecuteAction::CreateSP( this, &FSequencer::TrimSection, true ),
FCanExecuteAction::CreateLambda(CanTrimSection));
SequencerCommandBindings->MapAction(
Commands.TrimSectionRight,
FExecuteAction::CreateSP( this, &FSequencer::TrimSection, false ),
FCanExecuteAction::CreateLambda(CanTrimSection));
SequencerCommandBindings->MapAction(
Commands.TrimOrExtendSectionLeft,
FExecuteAction::CreateSP( this, &FSequencer::TrimOrExtendSection, true ) );
SequencerCommandBindings->MapAction(
Commands.TrimOrExtendSectionRight,
FExecuteAction::CreateSP( this, &FSequencer::TrimOrExtendSection, false ) );
SequencerCommandBindings->MapAction(
Commands.SplitSection,
FExecuteAction::CreateSP( this, &FSequencer::SplitSection ),
FCanExecuteAction::CreateLambda(CanTrimSection));
// We can convert to spawnables if anything selected is a root-level possessable
auto CanConvertToSpawnables = [this]{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if (!Sequence || !Sequence->AllowsSpawnableObjects())
{
return false;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
FMovieScenePossessable* Possessable = MovieScene->FindPossessable(ObjectBindingNode->GetObjectGuid());
if (Possessable && !Possessable->GetParent().IsValid())
{
return true;
}
}
}
return false;
};
SequencerCommandBindings->MapAction(
FSequencerCommands::Get().ConvertToSpawnable,
FExecuteAction::CreateSP(this, &FSequencer::ConvertSelectedNodesToSpawnables),
FCanExecuteAction::CreateLambda(CanConvertToSpawnables)
);
auto AreConvertableSpawnablesSelected = [this] {
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid());
if (Spawnable && SpawnRegister->CanConvertSpawnableToPossessable(*Spawnable))
{
return true;
}
}
}
return false;
};
SequencerCommandBindings->MapAction(
FSequencerCommands::Get().ConvertToPossessable,
FExecuteAction::CreateSP(this, &FSequencer::ConvertSelectedNodesToPossessables),
FCanExecuteAction::CreateLambda(AreConvertableSpawnablesSelected)
);
auto AreSpawnablesSelected = [this] {
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (IObjectBindingExtension* ObjectBindingNode = ICastable::CastWeakPtr<IObjectBindingExtension>(Node))
{
FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid());
if (Spawnable)
{
return true;
}
}
}
return false;
};
SequencerCommandBindings->MapAction(
FSequencerCommands::Get().SaveCurrentSpawnableState,
FExecuteAction::CreateSP(this, &FSequencer::SaveSelectedNodesSpawnableState),
FCanExecuteAction::CreateLambda(AreSpawnablesSelected)
);
SequencerCommandBindings->MapAction(
FSequencerCommands::Get().RestoreAnimatedState,
FExecuteAction::CreateSP(this, &FSequencer::RestorePreAnimatedState)
);
SequencerCommandBindings->MapAction(
Commands.SetAutoKey,
FExecuteAction::CreateLambda( [this]{ Settings->SetAutoChangeMode( EAutoChangeMode::AutoKey ); } ),
FCanExecuteAction::CreateLambda( [this]{ return Settings->GetAllowEditsMode() != EAllowEditsMode::AllowLevelEditsOnly; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetAutoChangeMode() == EAutoChangeMode::AutoKey; } ) );
SequencerCommandBindings->MapAction(
Commands.SetAutoTrack,
FExecuteAction::CreateLambda([this] { Settings->SetAutoChangeMode(EAutoChangeMode::AutoTrack); } ),
FCanExecuteAction::CreateLambda([this] { return Settings->GetAllowEditsMode() != EAllowEditsMode::AllowLevelEditsOnly; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetAutoChangeMode() == EAutoChangeMode::AutoTrack; } ) );
SequencerCommandBindings->MapAction(
Commands.SetAutoChangeAll,
FExecuteAction::CreateLambda([this] { Settings->SetAutoChangeMode(EAutoChangeMode::All); } ),
FCanExecuteAction::CreateLambda([this] { return Settings->GetAllowEditsMode() != EAllowEditsMode::AllowLevelEditsOnly; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetAutoChangeMode() == EAutoChangeMode::All; } ) );
SequencerCommandBindings->MapAction(
Commands.SetAutoChangeNone,
FExecuteAction::CreateLambda([this] { Settings->SetAutoChangeMode(EAutoChangeMode::None); } ),
FCanExecuteAction::CreateLambda([this] { return Settings->GetAllowEditsMode() != EAllowEditsMode::AllowLevelEditsOnly; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetAutoChangeMode() == EAutoChangeMode::None; } ) );
SequencerCommandBindings->MapAction(
Commands.AllowAllEdits,
FExecuteAction::CreateLambda( [this]{ Settings->SetAllowEditsMode( EAllowEditsMode::AllEdits ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetAllowEditsMode() == EAllowEditsMode::AllEdits; } ) );
SequencerCommandBindings->MapAction(
Commands.AllowSequencerEditsOnly,
FExecuteAction::CreateLambda([this] { Settings->SetAllowEditsMode(EAllowEditsMode::AllowSequencerEditsOnly); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetAllowEditsMode() == EAllowEditsMode::AllowSequencerEditsOnly; }));
SequencerCommandBindings->MapAction(
Commands.AllowLevelEditsOnly,
FExecuteAction::CreateLambda([this] { Settings->SetAllowEditsMode(EAllowEditsMode::AllowLevelEditsOnly); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetAllowEditsMode() == EAllowEditsMode::AllowLevelEditsOnly; }));
SequencerCommandBindings->MapAction(
Commands.ToggleAutoKeyEnabled,
FExecuteAction::CreateLambda( [this]{ Settings->SetAutoChangeMode(Settings->GetAutoChangeMode() == EAutoChangeMode::None ? EAutoChangeMode::AutoKey : EAutoChangeMode::None); } ),
FCanExecuteAction::CreateLambda( [this]{ return Settings->GetAllowEditsMode() != EAllowEditsMode::AllowLevelEditsOnly; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetAutoChangeMode() == EAutoChangeMode::AutoKey; } ) );
SequencerCommandBindings->MapAction(
Commands.SetKeyChanged,
FExecuteAction::CreateLambda([this] { Settings->SetKeyGroupMode(EKeyGroupMode::KeyChanged); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetKeyGroupMode() == EKeyGroupMode::KeyChanged; }));
SequencerCommandBindings->MapAction(
Commands.SetKeyGroup,
FExecuteAction::CreateLambda([this] { Settings->SetKeyGroupMode(EKeyGroupMode::KeyGroup); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetKeyGroupMode() == EKeyGroupMode::KeyGroup; }));
SequencerCommandBindings->MapAction(
Commands.SetKeyAll,
FExecuteAction::CreateLambda([this] { Settings->SetKeyGroupMode(EKeyGroupMode::KeyAll); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetKeyGroupMode() == EKeyGroupMode::KeyAll; }));
SequencerCommandBindings->MapAction(
Commands.ToggleMarkAtPlayPosition,
FExecuteAction::CreateSP( this, &FSequencer::ToggleMarkAtPlayPosition));
SequencerCommandBindings->MapAction(
Commands.StepToNextMark,
FExecuteAction::CreateSP( this, &FSequencer::StepToNextMark));
SequencerCommandBindings->MapAction(
Commands.StepToPreviousMark,
FExecuteAction::CreateSP( this, &FSequencer::StepToPreviousMark));
SequencerCommandBindings->MapAction(
Commands.ToggleAutoScroll,
FExecuteAction::CreateLambda( [this]{ Settings->SetAutoScrollEnabled( !Settings->GetAutoScrollEnabled() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetAutoScrollEnabled(); } ) );
SequencerCommandBindings->MapAction(
Commands.FindInContentBrowser,
FExecuteAction::CreateSP( this, &FSequencer::FindInContentBrowser ) );
SequencerCommandBindings->MapAction(
Commands.ToggleCombinedKeyframes,
FExecuteAction::CreateLambda( [this]{
Settings->SetShowCombinedKeyframes( !Settings->GetShowCombinedKeyframes() );
} ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowCombinedKeyframes(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleChannelColors,
FExecuteAction::CreateLambda( [this]{
Settings->SetShowChannelColors( !Settings->GetShowChannelColors() );
} ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowChannelColors(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleShowStatusBar,
FExecuteAction::CreateLambda([this] {
Settings->SetShowStatusBar(!Settings->GetShowStatusBar());
}),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetShowStatusBar(); }));
SequencerCommandBindings->MapAction(
Commands.ToggleShowSelectedNodesOnly,
FExecuteAction::CreateLambda( [this]{
Settings->SetShowSelectedNodesOnly( !Settings->GetShowSelectedNodesOnly() );
} ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowSelectedNodesOnly(); } ) );
SequencerCommandBindings->MapAction(
Commands.ChangeTimeDisplayFormat,
FExecuteAction::CreateLambda( [this]{
EFrameNumberDisplayFormats NextFormat = (EFrameNumberDisplayFormats)((uint8)Settings->GetTimeDisplayFormat() + 1);
if (NextFormat == EFrameNumberDisplayFormats::MAX_Count)
{
NextFormat = EFrameNumberDisplayFormats::NonDropFrameTimecode;
}
// If the next framerate in the list is drop format timecode and we're not in a play rate that supports drop format timecode,
// then we will skip over it.
bool bCanShowDropFrameTimecode = FTimecode::UseDropFormatTimecode(GetFocusedDisplayRate());
if (bCanShowDropFrameTimecode && NextFormat == EFrameNumberDisplayFormats::NonDropFrameTimecode)
{
NextFormat = EFrameNumberDisplayFormats::DropFrameTimecode;
}
else if (!bCanShowDropFrameTimecode && NextFormat == EFrameNumberDisplayFormats::DropFrameTimecode)
{
NextFormat = EFrameNumberDisplayFormats::Seconds;
}
Settings->SetTimeDisplayFormat( NextFormat );
} ),
FCanExecuteAction::CreateLambda([] { return true; }));
SequencerCommandBindings->MapAction(
Commands.ToggleShowRangeSlider,
FExecuteAction::CreateLambda( [this]{ Settings->SetShowRangeSlider( !Settings->GetShowRangeSlider() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowRangeSlider(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleIsSnapEnabled,
FExecuteAction::CreateLambda( [this]{ Settings->SetIsSnapEnabled( !Settings->GetIsSnapEnabled() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetIsSnapEnabled(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapKeyTimesToInterval,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapKeyTimesToInterval( !Settings->GetSnapKeyTimesToInterval() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapKeyTimesToInterval(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapKeyTimesToKeys,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapKeyTimesToKeys( !Settings->GetSnapKeyTimesToKeys() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapKeyTimesToKeys(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapSectionTimesToInterval,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapSectionTimesToInterval( !Settings->GetSnapSectionTimesToInterval() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapSectionTimesToInterval(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapSectionTimesToSections,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapSectionTimesToSections( !Settings->GetSnapSectionTimesToSections() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapSectionTimesToSections(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapKeysAndSectionsToPlayRange,
FExecuteAction::CreateLambda([this] { Settings->SetSnapKeysAndSectionsToPlayRange(!Settings->GetSnapKeysAndSectionsToPlayRange()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetSnapKeysAndSectionsToPlayRange(); }));
SequencerCommandBindings->MapAction(
Commands.ToggleSnapPlayTimeToKeys,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapPlayTimeToKeys( !Settings->GetSnapPlayTimeToKeys() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapPlayTimeToKeys(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapPlayTimeToSections,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapPlayTimeToSections( !Settings->GetSnapPlayTimeToSections() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapPlayTimeToSections(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapPlayTimeToMarkers,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapPlayTimeToMarkers( !Settings->GetSnapPlayTimeToMarkers() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapPlayTimeToMarkers(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapPlayTimeToInterval,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapPlayTimeToInterval( !Settings->GetSnapPlayTimeToInterval() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapPlayTimeToInterval(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapPlayTimeToPressedKey,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapPlayTimeToPressedKey( !Settings->GetSnapPlayTimeToPressedKey() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapPlayTimeToPressedKey(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapPlayTimeToDraggedKey,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapPlayTimeToDraggedKey( !Settings->GetSnapPlayTimeToDraggedKey() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapPlayTimeToDraggedKey(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapCurveValueToInterval,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapCurveValueToInterval( !Settings->GetSnapCurveValueToInterval() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapCurveValueToInterval(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleShowCurveEditor,
FExecuteAction::CreateLambda( [this]{ SetShowCurveEditor(!GetCurveEditorIsVisible()); } ),
FCanExecuteAction::CreateLambda( [this]{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return GetCurveEditorIsVisible(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleLinkCurveEditorTimeRange,
FExecuteAction::CreateLambda( [this]{ Settings->SetLinkCurveEditorTimeRange(!Settings->GetLinkCurveEditorTimeRange()); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetLinkCurveEditorTimeRange(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleShowPreAndPostRoll,
FExecuteAction::CreateLambda( [this]{ Settings->SetShouldShowPrePostRoll(!Settings->ShouldShowPrePostRoll()); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->ShouldShowPrePostRoll(); } ) );
auto CanCutOrCopy = [this]{
// For copy tracks
TSet<TWeakPtr<FViewModel>> SelectedNodes = Selection.GetNodesWithSelectedKeysOrSections();
// If this is empty then we are selecting display nodes
if (SelectedNodes.Num() == 0)
{
SelectedNodes = Selection.GetSelectedOutlinerItems();
for (TWeakPtr<FViewModel> WeakNode : SelectedNodes)
{
TSharedPtr<FViewModel> Node = WeakNode.Pin();
if (Node->IsA<ITrackExtension>() || Node->IsA<IObjectBindingExtension>() || Node->IsA<FFolderModel>())
{
// if contains one node that can be copied we allow the action
// later on we will filter out the invalid nodes in CopySelection() or CutSelection()
return true;
}
else if (Node->GetParent().IsValid() && Node->GetParent()->IsA<ITrackExtension>() && !Node->IsA<FCategoryGroupModel>())
{
return true;
}
}
return false;
}
UMovieSceneTrack* Track = nullptr;
for (FSequencerSelectedKey Key : Selection.GetSelectedKeys())
{
if (!Track)
{
Track = Key.Section->GetTypedOuter<UMovieSceneTrack>();
}
if (!Track || Track != Key.Section->GetTypedOuter<UMovieSceneTrack>())
{
return false;
}
}
return true;
};
auto CanDelete = [this]{
return Selection.GetSelectedKeys().Num() || Selection.GetSelectedSections().Num() || Selection.GetSelectedOutlinerItems().Num();
};
auto CanDuplicate = [this]{
if (Selection.GetSelectedKeys().Num() || Selection.GetSelectedSections().Num() || Selection.GetSelectedTracks().Num())
{
return true;
}
// For duplicate object tracks
TArray<TSharedRef<FViewModel>> SelectedNodes;
Selection.GetNodesWithSelectedKeysOrSections(SelectedNodes);
if (SelectedNodes.Num() == 0)
{
Selection.GetSelectedOutlinerItems(SelectedNodes);
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
if (Node->IsA<IObjectBindingExtension>())
{
// if contains one node that can be copied we allow the action
return true;
}
}
return false;
}
return false;
};
auto IsSelectionRangeNonEmpty = [this]{
UMovieSceneSequence* EditedSequence = GetFocusedMovieSceneSequence();
if (!EditedSequence || !EditedSequence->GetMovieScene())
{
return false;
}
return !EditedSequence->GetMovieScene()->GetSelectionRange().IsEmpty();
};
SequencerCommandBindings->MapAction(
FGenericCommands::Get().Rename,
FExecuteAction::CreateLambda([this]
{
for (TWeakPtr<FViewModel> Item : Selection.GetSelectedOutlinerItems())
{
IRenameableExtension* Renamable = ICastable::CastWeakPtr<IRenameableExtension>(Item);
if (Renamable && Renamable->CanRename())
{
Renamable->OnRenameRequested().Broadcast();
}
}
}),
FCanExecuteAction::CreateLambda([this]
{
for (TWeakPtr<FViewModel> Item : Selection.GetSelectedOutlinerItems())
{
IRenameableExtension* Renamable = ICastable::CastWeakPtr<IRenameableExtension>(Item);
if (Renamable && Renamable->CanRename())
{
return true;
}
}
return false;
})
);
SequencerCommandBindings->MapAction(
FGenericCommands::Get().Cut,
FExecuteAction::CreateSP(this, &FSequencer::CutSelection),
FCanExecuteAction::CreateLambda(CanCutOrCopy)
);
SequencerCommandBindings->MapAction(
FGenericCommands::Get().Copy,
FExecuteAction::CreateSP(this, &FSequencer::CopySelection),
FCanExecuteAction::CreateLambda(CanCutOrCopy)
);
SequencerCommandBindings->MapAction(
FGenericCommands::Get().Duplicate,
FExecuteAction::CreateSP(this, &FSequencer::DuplicateSelection),
FCanExecuteAction::CreateLambda(CanDuplicate)
);
SequencerCommandBindings->MapAction(
Commands.TogglePlaybackRangeLocked,
FExecuteAction::CreateSP( this, &FSequencer::TogglePlaybackRangeLocked ),
FCanExecuteAction::CreateLambda( [this] { return GetFocusedMovieSceneSequence() != nullptr; } ),
FIsActionChecked::CreateSP( this, &FSequencer::IsPlaybackRangeLocked ));
SequencerCommandBindings->MapAction(
Commands.ToggleCleanPlaybackMode,
FExecuteAction::CreateLambda( [this]{ Settings->SetCleanPlaybackMode( !Settings->GetCleanPlaybackMode() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetCleanPlaybackMode(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleRerunConstructionScripts,
FExecuteAction::CreateLambda( [this]{ Settings->SetRerunConstructionScripts( !Settings->ShouldRerunConstructionScripts() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->ShouldRerunConstructionScripts(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleAsyncEvaluation,
FExecuteAction::CreateLambda( [this]{ this->ToggleAsyncEvaluation(); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return this->UsesAsyncEvaluation(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleKeepCursorInPlaybackRangeWhileScrubbing,
FExecuteAction::CreateLambda([this] { Settings->SetKeepCursorInPlayRangeWhileScrubbing(!Settings->ShouldKeepCursorInPlayRangeWhileScrubbing()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->ShouldKeepCursorInPlayRangeWhileScrubbing(); }));
SequencerCommandBindings->MapAction(
Commands.ToggleKeepPlaybackRangeInSectionBounds,
FExecuteAction::CreateLambda( [this]{ Settings->SetKeepPlayRangeInSectionBounds( !Settings->ShouldKeepPlayRangeInSectionBounds() ); NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->ShouldKeepPlayRangeInSectionBounds(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleEvaluateSubSequencesInIsolation,
FExecuteAction::CreateLambda( [this]{
const bool bNewValue = !Settings->ShouldEvaluateSubSequencesInIsolation();
Settings->SetEvaluateSubSequencesInIsolation( bNewValue );
FMovieSceneSequenceID NewOverrideRoot = bNewValue ? ActiveTemplateIDs.Top() : MovieSceneSequenceID::Root;
UMovieSceneEntitySystemLinker* Linker = RootTemplateInstance.GetEntitySystemLinker();
RootTemplateInstance.FindInstance(MovieSceneSequenceID::Root)->OverrideRootSequence(Linker, NewOverrideRoot);
ForceEvaluate();
} ),
FCanExecuteAction::CreateLambda( [this]{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->ShouldEvaluateSubSequencesInIsolation(); } ) );
SequencerCommandBindings->MapAction(
Commands.RenderMovie,
FExecuteAction::CreateLambda([this]{ RenderMovieInternal(GetPlaybackRange()); })
);
SequencerCommandBindings->MapAction(
Commands.CreateCamera,
FExecuteAction::CreateSP(this, &FSequencer::CreateCamera),
FCanExecuteAction(),
FIsActionChecked(),
FIsActionButtonVisible::CreateLambda([this] { return ExactCast<ULevelSequence>(GetFocusedMovieSceneSequence()) != nullptr && IVREditorModule::Get().IsVREditorModeActive() == false; }) //@todo VREditor: Creating a camera while in VR mode disrupts the hmd. This is a temporary fix by hiding the button when in VR mode.
);
SequencerCommandBindings->MapAction(
Commands.FixPossessableObjectClass,
FExecuteAction::CreateSP( this, &FSequencer::FixPossessableObjectClass ),
FCanExecuteAction::CreateLambda( []{ return true; } ) );
SequencerCommandBindings->MapAction(
Commands.RebindPossessableReferences,
FExecuteAction::CreateSP( this, &FSequencer::RebindPossessableReferences ),
FCanExecuteAction::CreateLambda( []{ return true; } ) );
SequencerCommandBindings->MapAction(
Commands.ImportFBX,
FExecuteAction::CreateSP( this, &FSequencer::ImportFBX ),
FCanExecuteAction::CreateLambda( [] { return true; } ) );
SequencerCommandBindings->MapAction(
Commands.ExportFBX,
FExecuteAction::CreateSP( this, &FSequencer::ExportFBX ),
FCanExecuteAction::CreateLambda( [] { return true; } ) );
SequencerCommandBindings->MapAction(
Commands.ExportToCameraAnim,
FExecuteAction::CreateSP( this, &FSequencer::ExportToCameraAnim ),
FCanExecuteAction::CreateLambda( [] { return true; } ) );
SequencerCommandBindings->MapAction(
Commands.MoveToNewFolder,
FExecuteAction::CreateSP( this, &FSequencer::MoveSelectedNodesToNewFolder ),
FCanExecuteAction::CreateLambda( [this]{ return (GetSelectedNodesToMove().Num() > 0); } ) );
SequencerCommandBindings->MapAction(
Commands.RemoveFromFolder,
FExecuteAction::CreateSP( this, &FSequencer::RemoveSelectedNodesFromFolders ),
FCanExecuteAction::CreateLambda( [this]{ return (GetSelectedNodesInFolders().Num() > 0); } ) );
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
TrackEditors[i]->BindCommands(SequencerCommandBindings);
}
SequencerCommandBindings->MapAction(
Commands.AddTransformKey,
FExecuteAction::CreateSP(this, &FSequencer::OnAddTransformKeysForSelectedObjects, EMovieSceneTransformChannel::All),
FCanExecuteAction::CreateSP(this, &FSequencer::CanAddTransformKeysForSelectedObjects));
SequencerCommandBindings->MapAction(
Commands.AddTranslationKey,
FExecuteAction::CreateSP(this, &FSequencer::OnAddTransformKeysForSelectedObjects, EMovieSceneTransformChannel::Translation),
FCanExecuteAction::CreateSP(this, &FSequencer::CanAddTransformKeysForSelectedObjects));
SequencerCommandBindings->MapAction(
Commands.AddRotationKey,
FExecuteAction::CreateSP(this, &FSequencer::OnAddTransformKeysForSelectedObjects, EMovieSceneTransformChannel::Rotation),
FCanExecuteAction::CreateSP(this, &FSequencer::CanAddTransformKeysForSelectedObjects));
SequencerCommandBindings->MapAction(
Commands.AddScaleKey,
FExecuteAction::CreateSP(this, &FSequencer::OnAddTransformKeysForSelectedObjects, EMovieSceneTransformChannel::Scale),
FCanExecuteAction::CreateSP(this, &FSequencer::CanAddTransformKeysForSelectedObjects));
SequencerCommandBindings->MapAction(
Commands.TogglePilotCamera,
FExecuteAction::CreateSP(this, &FSequencer::OnTogglePilotCamera),
FCanExecuteAction::CreateLambda( [] { return true; } ),
FIsActionChecked::CreateSP(this, &FSequencer::IsPilotCamera));
// copy subset of sequencer commands to shared commands
*SequencerSharedBindings = *SequencerCommandBindings;
// Sequencer-only bindings
SequencerCommandBindings->MapAction(
FGenericCommands::Get().Delete,
FExecuteAction::CreateSP( this, &FSequencer::DeleteSelectedItems ),
FCanExecuteAction::CreateLambda(CanDelete));
SequencerCommandBindings->MapAction(
Commands.TogglePlay,
FExecuteAction::CreateSP(this, &FSequencer::TogglePlay));
SequencerCommandBindings->MapAction(
Commands.PlayForward,
FExecuteAction::CreateLambda([this] { OnPlayForward(false); }));
SequencerCommandBindings->MapAction(
Commands.JumpToStart,
FExecuteAction::CreateSP(this, &FSequencer::JumpToStart));
SequencerCommandBindings->MapAction(
Commands.JumpToEnd,
FExecuteAction::CreateSP(this, &FSequencer::JumpToEnd));
SequencerCommandBindings->MapAction(
Commands.StepForward,
FExecuteAction::CreateSP(this, &FSequencer::StepForward),
EUIActionRepeatMode::RepeatEnabled);
SequencerCommandBindings->MapAction(
Commands.StepBackward,
FExecuteAction::CreateSP(this, &FSequencer::StepBackward),
EUIActionRepeatMode::RepeatEnabled);
SequencerCommandBindings->MapAction(
Commands.JumpForward,
FExecuteAction::CreateSP(this, &FSequencer::JumpForward),
EUIActionRepeatMode::RepeatEnabled);
SequencerCommandBindings->MapAction(
Commands.JumpBackward,
FExecuteAction::CreateSP(this, &FSequencer::JumpBackward),
EUIActionRepeatMode::RepeatEnabled);
SequencerCommandBindings->MapAction(
Commands.SetInterpolationCubicAuto,
FExecuteAction::CreateSP(this, &FSequencer::SetInterpTangentMode, ERichCurveInterpMode::RCIM_Cubic, ERichCurveTangentMode::RCTM_Auto));
SequencerCommandBindings->MapAction(
Commands.SetInterpolationCubicUser,
FExecuteAction::CreateSP(this, &FSequencer::SetInterpTangentMode, ERichCurveInterpMode::RCIM_Cubic, ERichCurveTangentMode::RCTM_User));
SequencerCommandBindings->MapAction(
Commands.SetInterpolationCubicBreak,
FExecuteAction::CreateSP(this, &FSequencer::SetInterpTangentMode, ERichCurveInterpMode::RCIM_Cubic, ERichCurveTangentMode::RCTM_Break));
SequencerCommandBindings->MapAction(
Commands.ToggleWeightedTangents,
FExecuteAction::CreateSP(this, &FSequencer::ToggleInterpTangentWeightMode));
SequencerCommandBindings->MapAction(
Commands.SetInterpolationLinear,
FExecuteAction::CreateSP(this, &FSequencer::SetInterpTangentMode, ERichCurveInterpMode::RCIM_Linear, ERichCurveTangentMode::RCTM_Auto));
SequencerCommandBindings->MapAction(
Commands.SetInterpolationConstant,
FExecuteAction::CreateSP(this, &FSequencer::SetInterpTangentMode, ERichCurveInterpMode::RCIM_Constant, ERichCurveTangentMode::RCTM_Auto));
SequencerCommandBindings->MapAction(
Commands.ShuttleForward,
FExecuteAction::CreateSP( this, &FSequencer::ShuttleForward ));
SequencerCommandBindings->MapAction(
Commands.RestorePlaybackSpeed,
FExecuteAction::CreateSP(this, &FSequencer::RestorePlaybackSpeed));
SequencerCommandBindings->MapAction(
Commands.ShuttleBackward,
FExecuteAction::CreateSP( this, &FSequencer::ShuttleBackward ));
SequencerCommandBindings->MapAction(
Commands.Pause,
FExecuteAction::CreateSP( this, &FSequencer::Pause ));
SequencerCommandBindings->MapAction(
Commands.SetSelectionRangeEnd,
FExecuteAction::CreateLambda([this]{ SetSelectionRangeEnd(GetLocalTime().Time); }));
SequencerCommandBindings->MapAction(
Commands.SetSelectionRangeStart,
FExecuteAction::CreateLambda([this]{ SetSelectionRangeStart(GetLocalTime().Time); }));
SequencerCommandBindings->MapAction(
Commands.ClearSelectionRange,
FExecuteAction::CreateLambda([this]{ ClearSelectionRange(); }),
FCanExecuteAction::CreateLambda(IsSelectionRangeNonEmpty));
SequencerCommandBindings->MapAction(
Commands.SelectKeysInSelectionRange,
FExecuteAction::CreateSP(this, &FSequencer::SelectInSelectionRange, true, false),
FCanExecuteAction::CreateLambda(IsSelectionRangeNonEmpty));
SequencerCommandBindings->MapAction(
Commands.SelectSectionsInSelectionRange,
FExecuteAction::CreateSP(this, &FSequencer::SelectInSelectionRange, false, true),
FCanExecuteAction::CreateLambda(IsSelectionRangeNonEmpty));
SequencerCommandBindings->MapAction(
Commands.SelectAllInSelectionRange,
FExecuteAction::CreateSP(this, &FSequencer::SelectInSelectionRange, true, true),
FCanExecuteAction::CreateLambda(IsSelectionRangeNonEmpty));
SequencerCommandBindings->MapAction(
Commands.SelectForward,
FExecuteAction::CreateSP(this, &FSequencer::SelectForward));
SequencerCommandBindings->MapAction(
Commands.SelectBackward,
FExecuteAction::CreateSP(this, &FSequencer::SelectBackward));
SequencerCommandBindings->MapAction(
Commands.StepToNextShot,
FExecuteAction::CreateSP( this, &FSequencer::StepToNextShot ) );
SequencerCommandBindings->MapAction(
Commands.StepToPreviousShot,
FExecuteAction::CreateSP( this, &FSequencer::StepToPreviousShot ) );
SequencerCommandBindings->MapAction(
Commands.NavigateForward,
FExecuteAction::CreateLambda([this] { NavigateForward(); }),
FCanExecuteAction::CreateLambda([this] { return CanNavigateForward(); }));
SequencerCommandBindings->MapAction(
Commands.NavigateBackward,
FExecuteAction::CreateLambda([this] { NavigateBackward(); }),
FCanExecuteAction::CreateLambda([this] { return CanNavigateBackward(); }));
SequencerCommandBindings->MapAction(
Commands.SetStartPlaybackRange,
FExecuteAction::CreateLambda([this] { SetPlaybackStart(); }) );
SequencerCommandBindings->MapAction(
Commands.ResetViewRange,
FExecuteAction::CreateSP( this, &FSequencer::ResetViewRange ) );
SequencerCommandBindings->MapAction(
Commands.ZoomToFit,
FExecuteAction::CreateSP( this, &FSequencer::ZoomToFit ) );
SequencerCommandBindings->MapAction(
Commands.ZoomInViewRange,
FExecuteAction::CreateSP( this, &FSequencer::ZoomInViewRange ),
FCanExecuteAction(),
EUIActionRepeatMode::RepeatEnabled );
SequencerCommandBindings->MapAction(
Commands.ZoomOutViewRange,
FExecuteAction::CreateSP( this, &FSequencer::ZoomOutViewRange ),
FCanExecuteAction(),
EUIActionRepeatMode::RepeatEnabled );
SequencerCommandBindings->MapAction(
Commands.SetEndPlaybackRange,
FExecuteAction::CreateLambda([this] { SetPlaybackEnd(); }) );
SequencerCommandBindings->MapAction(
Commands.SetSelectionRangeToNextShot,
FExecuteAction::CreateSP( this, &FSequencer::SetSelectionRangeToShot, true ),
FCanExecuteAction::CreateSP( this, &FSequencer::IsViewingMasterSequence ) );
SequencerCommandBindings->MapAction(
Commands.SetSelectionRangeToPreviousShot,
FExecuteAction::CreateSP( this, &FSequencer::SetSelectionRangeToShot, false ),
FCanExecuteAction::CreateSP( this, &FSequencer::IsViewingMasterSequence ) );
SequencerCommandBindings->MapAction(
Commands.SetPlaybackRangeToAllShots,
FExecuteAction::CreateSP( this, &FSequencer::SetPlaybackRangeToAllShots ),
FCanExecuteAction::CreateSP( this, &FSequencer::IsViewingMasterSequence ) );
SequencerCommandBindings->MapAction(
Commands.RefreshUI,
FExecuteAction::CreateLambda(
[this]
{
this->Selection.Empty();
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
FMovieSceneSequenceID SequenceID = GetFocusedTemplateID();
TSharedPtr<FSequenceModel> RootSequenceModel = this->ViewModel->GetRootModel().ImplicitCast();
RootSequenceModel->SetSequence(nullptr, MovieSceneSequenceID::Root);
RootSequenceModel->SetSequence(FocusedSequence, SequenceID);
RefreshTree();
}
),
FCanExecuteAction());
// We want a subset of the commands to work in the Curve Editor too, but bound to our functions. This minimizes code duplication
// while also freeing us up from issues that result from Sequencer already using two lists (for which our commands might be spread
// across both lists which makes a direct copy like it already uses difficult).
CurveEditorSharedBindings->MapAction(Commands.TogglePlay, *SequencerCommandBindings->GetActionForCommand(Commands.TogglePlay));
CurveEditorSharedBindings->MapAction(Commands.TogglePlayViewport, *SequencerCommandBindings->GetActionForCommand(Commands.TogglePlayViewport));
CurveEditorSharedBindings->MapAction(Commands.PlayForward, *SequencerCommandBindings->GetActionForCommand(Commands.PlayForward));
CurveEditorSharedBindings->MapAction(Commands.JumpToStart, *SequencerCommandBindings->GetActionForCommand(Commands.JumpToStart));
CurveEditorSharedBindings->MapAction(Commands.JumpToEnd, *SequencerCommandBindings->GetActionForCommand(Commands.JumpToEnd));
CurveEditorSharedBindings->MapAction(Commands.ShuttleBackward, *SequencerCommandBindings->GetActionForCommand(Commands.ShuttleBackward));
CurveEditorSharedBindings->MapAction(Commands.ShuttleForward, *SequencerCommandBindings->GetActionForCommand(Commands.ShuttleForward));
CurveEditorSharedBindings->MapAction(Commands.Pause, *SequencerCommandBindings->GetActionForCommand(Commands.Pause));
CurveEditorSharedBindings->MapAction(Commands.StepForward, *SequencerCommandBindings->GetActionForCommand(Commands.StepForward));
CurveEditorSharedBindings->MapAction(Commands.StepBackward, *SequencerCommandBindings->GetActionForCommand(Commands.StepBackward));
CurveEditorSharedBindings->MapAction(Commands.StepForwardViewport, *SequencerCommandBindings->GetActionForCommand(Commands.StepForwardViewport));
CurveEditorSharedBindings->MapAction(Commands.StepBackwardViewport, *SequencerCommandBindings->GetActionForCommand(Commands.StepBackwardViewport));
CurveEditorSharedBindings->MapAction(Commands.JumpForward, *SequencerCommandBindings->GetActionForCommand(Commands.JumpForward));
CurveEditorSharedBindings->MapAction(Commands.JumpBackward, *SequencerCommandBindings->GetActionForCommand(Commands.JumpBackward));
CurveEditorSharedBindings->MapAction(Commands.StepToNextKey, *SequencerCommandBindings->GetActionForCommand(Commands.StepToNextKey));
CurveEditorSharedBindings->MapAction(Commands.StepToPreviousKey, *SequencerCommandBindings->GetActionForCommand(Commands.StepToPreviousKey));
CurveEditorSharedBindings->MapAction(Commands.AddTransformKey, *SequencerCommandBindings->GetActionForCommand(Commands.AddTransformKey));
CurveEditorSharedBindings->MapAction(Commands.AddTranslationKey, *SequencerCommandBindings->GetActionForCommand(Commands.AddTranslationKey));
CurveEditorSharedBindings->MapAction(Commands.AddRotationKey, *SequencerCommandBindings->GetActionForCommand(Commands.AddRotationKey));
CurveEditorSharedBindings->MapAction(Commands.AddScaleKey, *SequencerCommandBindings->GetActionForCommand(Commands.AddScaleKey));
CurveEditorSharedBindings->MapAction(Commands.ResetFilters, *SequencerCommandBindings->GetActionForCommand(Commands.ResetFilters));
GetCurveEditor()->GetCommands()->Append(CurveEditorSharedBindings);
// bind widget specific commands
SequencerWidget->BindCommands(SequencerCommandBindings, CurveEditorSharedBindings);
}
void FSequencer::BuildAddTrackMenu(class FMenuBuilder& MenuBuilder)
{
if (IsLevelEditorSequencer())
{
MenuBuilder.AddMenuEntry(
LOCTEXT("LoadRecording", "Load Recorded Data"),
LOCTEXT("LoadRecordingDataTooltip", "Load in saved data from a previous recording."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetTreeFolderOpen"),
FUIAction(FExecuteAction::CreateRaw(this, &FSequencer::OnLoadRecordedData)));
}
MenuBuilder.AddMenuEntry(
LOCTEXT( "AddFolder", "Add Folder" ),
LOCTEXT( "AddFolderToolTip", "Adds a new folder." ),
FSlateIcon( FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetTreeFolderOpen" ),
FUIAction( FExecuteAction::CreateRaw( this, &FSequencer::OnAddFolder ) ) );
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
if (TrackEditors[i]->SupportsSequence(GetFocusedMovieSceneSequence()))
{
TrackEditors[i]->BuildAddTrackMenu(MenuBuilder);
}
}
}
void FSequencer::BuildAddObjectBindingsMenu(class FMenuBuilder& MenuBuilder)
{
for (int32 i = 0; i < ObjectBindings.Num(); ++i)
{
if (ObjectBindings[i]->SupportsSequence(GetFocusedMovieSceneSequence()))
{
ObjectBindings[i]->BuildSequencerAddMenu(MenuBuilder);
}
}
}
void FSequencer::BuildObjectBindingTrackMenu(FMenuBuilder& MenuBuilder, const TArray<FGuid>& InObjectBindings, const UClass* ObjectClass)
{
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
TrackEditors[i]->BuildObjectBindingTrackMenu(MenuBuilder, InObjectBindings, ObjectClass);
}
}
void FSequencer::BuildObjectBindingEditButtons(TSharedPtr<SHorizontalBox> EditBox, const FGuid& ObjectBinding, const UClass* ObjectClass)
{
for (int32 i = 0; i < TrackEditors.Num(); ++i)
{
TrackEditors[i]->BuildObjectBindingEditButtons(EditBox, ObjectBinding, ObjectClass);
}
}
void FSequencer::BuildAddSelectedToFolderMenu(FMenuBuilder& MenuBuilder)
{
using namespace UE::Sequencer;
MenuBuilder.AddMenuEntry(
LOCTEXT("MoveNodesToNewFolder", "New Folder"),
LOCTEXT("MoveNodesToNewFolderTooltip", "Create a new folder and adds the selected nodes"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetTreeFolderOpen"),
FUIAction(
FExecuteAction::CreateSP(this, &FSequencer::MoveSelectedNodesToNewFolder),
FCanExecuteAction::CreateLambda( [this]{ return (GetSelectedNodesToMove().Num() > 0); })));
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = FocusedMovieSceneSequence ? FocusedMovieSceneSequence->GetMovieScene() : nullptr;
if (MovieScene)
{
TSharedRef<TArray<UMovieSceneFolder*>> ExcludedFolders = MakeShared<TArray<UMovieSceneFolder*> >();
for (TWeakPtr<FViewModel> Node : GetSelection().GetSelectedOutlinerItems())
{
if (FFolderModel* FolderNode = ICastable::CastWeakPtr<FFolderModel>(Node))
{
if (FolderNode->CanDrag())
{
ExcludedFolders->Add(FolderNode->GetFolder());
}
}
}
// Copy the list of root folders and remove any currently dragged folders. The rest represents the
// list of possible folders to move the selection into.
TArray<UMovieSceneFolder*> ChildFolders;
MovieScene->GetRootFolders(ChildFolders);
for (int32 Index = 0; Index < ChildFolders.Num(); ++Index)
{
if (ExcludedFolders->Contains(ChildFolders[Index]))
{
ChildFolders.RemoveAt(Index);
--Index;
}
}
if (ChildFolders.Num() > 0)
{
MenuBuilder.AddMenuSeparator();
}
for (UMovieSceneFolder* Folder : ChildFolders)
{
BuildAddSelectedToFolderMenuEntry(MenuBuilder, ExcludedFolders, Folder);
}
}
}
void FSequencer::BuildAddSelectedToFolderSubMenu(FMenuBuilder& InMenuBuilder, TSharedRef<TArray<UMovieSceneFolder*> >InExcludedFolders, UMovieSceneFolder* InFolder, TArray<UMovieSceneFolder*> InChildFolders)
{
InMenuBuilder.AddMenuEntry(
LOCTEXT("MoveNodesHere", "Move Here"),
LOCTEXT("MoveNodesHereTooltip", "Move the selected nodes to this existing folder"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::MoveSelectedNodesToFolder, InFolder)));
if (InChildFolders.Num() > 0)
{
InMenuBuilder.AddSeparator();
for (UMovieSceneFolder* Folder : InChildFolders)
{
BuildAddSelectedToFolderMenuEntry(InMenuBuilder, InExcludedFolders, Folder);
}
}
}
void FSequencer::BuildAddSelectedToFolderMenuEntry(FMenuBuilder& InMenuBuilder, TSharedRef<TArray<UMovieSceneFolder*> > InExcludedFolders, UMovieSceneFolder* InFolder)
{
TArray<UMovieSceneFolder*> ChildFolders;
for (UMovieSceneFolder* Folder : InFolder->GetChildFolders())
{
if (!InExcludedFolders->Contains(Folder))
{
ChildFolders.Add(Folder);
}
}
if (ChildFolders.Num() > 0)
{
InMenuBuilder.AddSubMenu(
FText::FromName(InFolder->GetFolderName()),
LOCTEXT("MoveNodesToFolderTooltip2", "Move the selected nodes to an existing folder"),
FNewMenuDelegate::CreateSP(this, &FSequencer::BuildAddSelectedToFolderSubMenu, InExcludedFolders, InFolder, ChildFolders));
}
else
{
InMenuBuilder.AddMenuEntry(
FText::FromName(InFolder->GetFolderName()),
LOCTEXT("MoveNodesToFolderTooltip1", "Move the selected nodes to this existing folder"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::MoveSelectedNodesToFolder, InFolder)));
}
}
void FSequencer::BuildAddSelectedToNodeGroupMenu(FMenuBuilder& MenuBuilder)
{
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = FocusedMovieSceneSequence ? FocusedMovieSceneSequence->GetMovieScene() : nullptr;
if (MovieScene)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("NewNodeGroup", "New Group"),
LOCTEXT("AddNodesToNewNodeGroupTooltip", "Creates a new group and adds the selected nodes"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::AddSelectedNodesToNewNodeGroup)));
if (MovieScene->GetNodeGroups().Num() > 0)
{
MenuBuilder.AddMenuSeparator();
for (UMovieSceneNodeGroup* NodeGroup : MovieScene->GetNodeGroups())
{
MenuBuilder.AddMenuEntry(
FText::FromName(NodeGroup->GetName()),
LOCTEXT("AddNodesToNodeGroupFormatTooltip", "Adds the selected nodes to this existing group"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::AddSelectedNodesToExistingNodeGroup, NodeGroup)));
}
}
}
}
void FSequencer::UpdateTimeBases()
{
UMovieSceneSequence* RootSequencePtr = GetRootMovieSceneSequence();
UMovieScene* RootMovieScene = RootSequencePtr ? RootSequencePtr->GetMovieScene() : nullptr;
if (RootMovieScene)
{
EMovieSceneEvaluationType EvaluationType = RootMovieScene->GetEvaluationType();
FFrameRate TickResolution = RootMovieScene->GetTickResolution();
FFrameRate DisplayRate = EvaluationType == EMovieSceneEvaluationType::FrameLocked ? RootMovieScene->GetDisplayRate() : TickResolution;
if (DisplayRate != PlayPosition.GetInputRate())
{
bNeedsEvaluate = true;
}
// We set the play position in terms of the display rate,
// but want evaluation ranges in the moviescene's tick resolution
PlayPosition.SetTimeBase(DisplayRate, TickResolution, EvaluationType);
}
}
void FSequencer::ResetTimeController()
{
UMovieScene* MovieScene = GetRootMovieSceneSequence()->GetMovieScene();
switch (MovieScene->GetClockSource())
{
case EUpdateClockSource::Audio: TimeController = MakeShared<FMovieSceneTimeController_AudioClock>(); break;
case EUpdateClockSource::Platform: TimeController = MakeShared<FMovieSceneTimeController_PlatformClock>(); break;
case EUpdateClockSource::RelativeTimecode: TimeController = MakeShared<FMovieSceneTimeController_RelativeTimecodeClock>(); break;
case EUpdateClockSource::Timecode: TimeController = MakeShared<FMovieSceneTimeController_TimecodeClock>(); break;
case EUpdateClockSource::PlayEveryFrame: TimeController = MakeShared<FMovieSceneTimeController_PlayEveryFrame>(); break;
case EUpdateClockSource::Custom: TimeController = MovieScene->MakeCustomTimeController(GetPlaybackContext()); break;
default: TimeController = MakeShared<FMovieSceneTimeController_Tick>(); break;
}
if (!TimeController)
{
TimeController = MakeShared<FMovieSceneTimeController_Tick>();
}
TimeController->PlayerStatusChanged(PlaybackState, GetGlobalTime());
}
void FSequencer::BuildCustomContextMenuForGuid(FMenuBuilder& MenuBuilder, FGuid ObjectBinding)
{
using namespace UE::Sequencer;
FSequencerOutlinerViewModel* OutlinerViewModel = ViewModel->GetOutliner()->CastThisChecked<FSequencerOutlinerViewModel>();
OutlinerViewModel->BuildCustomContextMenuForGuid(MenuBuilder, ObjectBinding);
}
FKeyAttributes FSequencer::GetDefaultKeyAttributes() const
{
switch (Settings->GetKeyInterpolation())
{
case EMovieSceneKeyInterpolation::User: return FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_User);
case EMovieSceneKeyInterpolation::Break: return FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_Break);
case EMovieSceneKeyInterpolation::Linear: return FKeyAttributes().SetInterpMode(RCIM_Linear).SetTangentMode(RCTM_Auto);
case EMovieSceneKeyInterpolation::Constant: return FKeyAttributes().SetInterpMode(RCIM_Constant).SetTangentMode(RCTM_Auto);
default: return FKeyAttributes().SetInterpMode(RCIM_Cubic).SetTangentMode(RCTM_Auto);
}
}
bool FSequencer::GetGridMetrics(const float PhysicalWidth, const double InViewStart, const double InViewEnd, double& OutMajorInterval, int32& OutMinorDivisions) const
{
FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);
TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
// Use the end of the view as the longest number
FString TickString = GetNumericTypeInterface()->ToString((InViewEnd * GetFocusedDisplayRate()).FrameNumber.Value);
FVector2D MaxTextSize = FontMeasureService->Measure(TickString, SmallLayoutFont);
static float MajorTickMultiplier = 2.f;
float MinTickPx = MaxTextSize.X + 5.f;
float DesiredMajorTickPx = MaxTextSize.X * MajorTickMultiplier;
if (PhysicalWidth > 0)
{
return GetFocusedDisplayRate().ComputeGridSpacing(
PhysicalWidth / (InViewEnd - InViewStart),
OutMajorInterval,
OutMinorDivisions,
MinTickPx,
DesiredMajorTickPx);
}
return false;
}
double FSequencer::GetDisplayRateDeltaFrameCount() const
{
return GetFocusedTickResolution().AsDecimal() * GetFocusedDisplayRate().AsInterval();
}
void FSequencer::RecompileDirtyDirectors()
{
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>("Sequencer");
TSet<UMovieSceneSequence*> AllSequences;
// Gather all sequences in the hierarchy
if (UMovieSceneSequence* Sequence = RootSequence.Get())
{
AllSequences.Add(Sequence);
}
const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(RootTemplateInstance.GetCompiledDataID());
if (Hierarchy)
{
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy->AllSubSequenceData())
{
if (UMovieSceneSequence* Sequence = Pair.Value.GetSequence())
{
AllSequences.Add(Sequence);
}
}
}
// Recompile them all if they are dirty
for (UMovieSceneSequence* Sequence : AllSequences)
{
FMovieSceneSequenceEditor* SequenceEditor = SequencerModule.FindSequenceEditor(Sequence->GetClass());
UBlueprint* DirectorBP = SequenceEditor ? SequenceEditor->FindDirectorBlueprint(Sequence) : nullptr;
if (DirectorBP && (DirectorBP->Status == BS_Unknown || DirectorBP->Status == BS_Dirty))
{
FKismetEditorUtilities::CompileBlueprint(DirectorBP);
}
}
}
void FSequencer::SetDisplayName(FGuid Binding, const FText& InDisplayName)
{
using namespace UE::Sequencer;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (FObjectBindingModel* ObjectBindingNode = ICastable::CastWeakPtr<FObjectBindingModel>(Node))
{
FGuid Guid = ObjectBindingNode->GetObjectGuid();
if (Guid == Binding)
{
ObjectBindingNode->Rename(InDisplayName);
break;
}
}
}
}
FText FSequencer::GetDisplayName(FGuid Binding)
{
using namespace UE::Sequencer;
for (const TWeakPtr<FViewModel>& Node : Selection.GetSelectedOutlinerItems())
{
if (FObjectBindingModel* ObjectBindingNode = ICastable::CastWeakPtr<FObjectBindingModel>(Node))
{
FGuid Guid = ObjectBindingNode->GetObjectGuid();
if (Guid == Binding)
{
return ObjectBindingNode->GetLabel();
}
}
}
return FText();
}
void FSequencer::OnCurveModelDisplayChanged(FCurveModel *InCurveModel, bool bDisplayed, const FCurveEditor* InCurveEditor)
{
OnCurveDisplayChanged.Broadcast(InCurveModel, bDisplayed, InCurveEditor);
}
void FSequencer::ToggleAsyncEvaluation()
{
UMovieSceneSequence* Sequence = GetRootMovieSceneSequence();
EMovieSceneSequenceFlags NewFlags = Sequence->GetFlags();
NewFlags ^= EMovieSceneSequenceFlags::BlockingEvaluation;
FScopedTransaction Transaction(EnumHasAnyFlags(NewFlags, EMovieSceneSequenceFlags::BlockingEvaluation) ? LOCTEXT("DisableAsyncEvaluation", "Disable Async Evaluation") : LOCTEXT("EnableAsyncEvaluation", "Enable Async Evaluation"));
Sequence->Modify();
Sequence->SetSequenceFlags(NewFlags);
}
bool FSequencer::UsesAsyncEvaluation()
{
return !EnumHasAnyFlags(GetRootMovieSceneSequence()->GetFlags(), EMovieSceneSequenceFlags::BlockingEvaluation);
}
#undef LOCTEXT_NAMESPACE