Files
2025-05-18 13:04:45 +08:00

12991 lines
405 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 "Async/ParallelFor.h"
#include "MVVM/SharedViewModelData.h"
#include "MVVM/Selection/Selection.h"
#include "MVVM/Extensions/IClockExtension.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/ViewModels/PossessableModel.h"
#include "MVVM/Views/SOutlinerView.h"
#include "MVVM/Extensions/IDeletableExtension.h"
#include "MVVM/Extensions/IClockExtension.h"
#include "MVVM/CurveEditorExtension.h"
#include "MVVM/CurveEditorIntegrationExtension.h"
#include "MVVM/ObjectBindingModelStorageExtension.h"
#include "MVVM/SectionModelStorageExtension.h"
#include "MVVM/TrackModelStorageExtension.h"
#include "MVVM/FolderModelStorageExtension.h"
#include "Camera/PlayerCameraManager.h"
#include "MovieSceneTransformTypes.h"
#include "Misc/MessageDialog.h"
#include "Misc/FeedbackContext.h"
#include "Misc/ScopedSlowTask.h"
#include "Misc/TransactionObjectEvent.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 "Channels/MovieSceneTimeWarpChannel.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 "Widgets/Input/SComboBox.h"
#include "Styling/AppStyle.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 "FileHelpers.h"
#include "UnrealEdGlobals.h"
#include "SequencerCommands.h"
#include "ISequencerSection.h"
#include "MovieSceneClipboard.h"
#include "SequencerCommonHelpers.h"
#include "SequencerMarkedFrameHelper.h"
#include "ISequencerNumericTypeInterface.h"
#include "SSequencer.h"
#include "SSequencerSection.h"
#include "SequencerKeyCollection.h"
#include "SequencerAddKeyOperation.h"
#include "SequencerPropertyKeyedStatus.h"
#include "SequencerSettings.h"
#include "SequencerLog.h"
#include "SequencerEdMode.h"
#include "MovieSceneBindingProxy.h"
#include "MovieSceneSequence.h"
#include "MovieSceneFolder.h"
#include "MovieSceneTracksSettings.h"
#include "PropertyEditorModule.h"
#include "PropertyHandle.h"
#include "EditorWidgetsModule.h"
#include "IAssetViewport.h"
#include "EditorSupportDelegates.h"
#include "MVVM/Views/SOutlinerView.h"
#include "ScopedTransaction.h"
#include "ISequencerTrackEditor.h"
#include "MovieSceneToolHelpers.h"
#include "Evaluation/ViewportSettingsPlaybackCapability.h"
#include "Sections/MovieSceneBoolSection.h"
#include "Sections/MovieScene3DTransformSection.h"
#include "Sections/MovieSceneSubSection.h"
#include "TimeSliderArgs.h"
#include "Tracks/MovieScene3DTransformTrack.h"
#include "Tracks/MovieSceneCinematicShotTrack.h"
#include "Tracks/MovieScenePropertyTrack.h"
#include "Tracks/MovieSceneSubTrack.h"
#include "Sections/MovieSceneCinematicShotSection.h"
#include "MovieSceneObjectBindingIDCustomization.h"
#include "MovieSceneObjectBindingID.h"
#include "ISettingsModule.h"
#include "Framework/Commands/GenericCommands.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 "CineCameraActor.h"
#include "CameraRig_Rail.h"
#include "CameraRig_Crane.h"
#include "DesktopPlatformModule.h"
#include "Factories.h"
#include "ObjectBindingTagCache.h"
#include "ISequencerEditorObjectBinding.h"
#include "LevelSequence.h"
#include "LevelSequenceActor.h"
#include "IVREditorModule.h"
#include "HAL/PlatformApplicationMisc.h"
#include "SequencerKeyActor.h"
#include "ISequencerChannelInterface.h"
#include "IMovieRendererInterface.h"
#include "MVVM/ViewModels/OutlinerColumns/IOutlinerColumn.h"
#include "MVVM/ViewModels/OutlinerIndicators/IOutlinerIndicatorBuilder.h"
#include "SequencerKeyCollection.h"
#include "CurveEditor.h"
#include "CurveEditorScreenSpace.h"
#include "CurveDataAbstraction.h"
#include "Fonts/FontMeasure.h"
#include "MovieSceneTimeHelpers.h"
#include "FrameNumberNumericInterface.h"
#include "FrameNumberDetailsCustomization.h"
#include "UObject/StrongObjectPtr.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 "SSequencerGroupManager.h"
#include "ActorTreeItem.h"
#include "Widgets/Layout/SSpacer.h"
#include "CurveEditorCommands.h"
#include "EntitySystem/MovieSceneEntitySystemRunner.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieScenePreAnimatedStateSystem.h"
#include "EntitySystem/MovieSceneSharedPlaybackState.h"
#include "Systems/MovieSceneMotionVectorSimulationSystem.h"
#include "IKeyArea.h"
#include "Editor/TransBuffer.h"
#include "Filters/SequencerFilterBar.h"
#include "Sidebar/SidebarDrawerConfig.h"
#include "Widgets/Sidebar/SequencerSelectionDrawer.h"
#include "Misc/Thumbnail/ThumbnailCaptureUtils.h"
#include "ToolMenus.h"
#include "SequencerToolMenuContext.h"
#include "Tools/SequencerSelectionAlignmentUtils.h"
#include "EngineModule.h"
#include "IViewportSelectableObject.h"
#include "Tracks/MovieSceneBindingLifetimeTrack.h"
#include "Sections/MovieSceneBindingLifetimeSection.h"
#include "Bindings/MovieSceneSpawnableBinding.h"
#include "Bindings/MovieSceneSpawnableActorBinding.h"
#include "Bindings/MovieSceneReplaceableActorBinding.h"
#include "TrackEditors/TimeWarpTrackEditor.h"
#include "Variants/MovieSceneTimeWarpGetter.h"
#define LOCTEXT_NAMESPACE "Sequencer"
DEFINE_LOG_CATEGORY(LogSequencer);
static TAutoConsoleVariable<bool> CVarAutoScrub(
TEXT("Sequencer.AutoScrub"),
false,
TEXT("Enable/disable auto-scrubbing"));
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"));
static TAutoConsoleVariable<bool> CVarTimeUndo(
TEXT("Sequencer.TimeUndo"),
false,
TEXT("Enable/disable ability to undo time when making changes"));
namespace UE
{
namespace Sequencer
{
struct FDeferredSignedObjectChangeHandler : UE::MovieScene::IDeferredSignedObjectChangeHandler
{
FDeferredSignedObjectChangeHandler()
{
Init();
}
~FDeferredSignedObjectChangeHandler()
{
if (UTransBuffer* TransBuffer = WeakBuffer.Get())
{
TransBuffer->OnTransactionStateChanged().RemoveAll(this);
}
}
void Init()
{
UTransBuffer* TransBuffer = GUnrealEd ? Cast<UTransBuffer>(GUnrealEd->Trans) : nullptr;
if (TransBuffer)
{
WeakBuffer = TransBuffer;
TransBuffer->OnTransactionStateChanged().AddRaw(this, &FDeferredSignedObjectChangeHandler::OnTransactionStateChanged);
if (TransBuffer->IsActive())
{
DeferTransactionChanges.Emplace();
}
}
else
{
FCoreDelegates::OnPostEngineInit.AddLambda([this] { this->Init(); });
}
}
void OnTransactionStateChanged(const FTransactionContext& TransactionContext, ETransactionStateEventType TransactionState)
{
/** A transaction has been started. This will be followed by a TransactionCanceled or TransactionFinalized event. */
switch (TransactionState)
{
case ETransactionStateEventType::TransactionStarted:
case ETransactionStateEventType::UndoRedoStarted:
DeferTransactionChanges.Emplace();
break;
case ETransactionStateEventType::TransactionCanceled:
case ETransactionStateEventType::PreTransactionFinalized:
case ETransactionStateEventType::UndoRedoFinalized:
DeferTransactionChanges.Reset();
break;
}
}
void Flush() override
{
TSet<TWeakObjectPtr<UMovieSceneSignedObject>> SignedObjectsTmp = SignedObjects;
SignedObjects.Empty();
// we operate on a copy of the signed objects in case the delegates would modify the array
for (TWeakObjectPtr<UMovieSceneSignedObject> WeakObject : SignedObjectsTmp)
{
if (UMovieSceneSignedObject* Object = WeakObject.Get())
{
Object->BroadcastChanged();
}
}
}
void DeferMarkAsChanged(UMovieSceneSignedObject* SignedObject) override
{
SignedObjects.Add(SignedObject);
}
bool CreateImplicitScopedModifyDefer() override
{
ensure(!DeferImplicitChanges.IsSet());
DeferImplicitChanges.Emplace();
return true;
}
void ResetImplicitScopedModifyDefer() override
{
DeferImplicitChanges.Reset();
}
TSet<TWeakObjectPtr<UMovieSceneSignedObject>> SignedObjects;
TWeakObjectPtr<UTransBuffer> WeakBuffer;
TOptional<UE::MovieScene::FScopedSignedObjectModifyDefer> DeferTransactionChanges;
TOptional<UE::MovieScene::FScopedSignedObjectModifyDefer> DeferImplicitChanges;
};
struct FPositionNumericTypeInterface : UE::Sequencer::FSequencerNumericTypeInterface
{
FPositionNumericTypeInterface(TSharedRef<FFrameNumberInterface> InInterface)
: FSequencerNumericTypeInterface(InInterface, ENumericIntent::Position)
{}
};
struct FDurationNumericTypeInterface : UE::Sequencer::FSequencerNumericTypeInterface
{
FDurationNumericTypeInterface(TSharedRef<FFrameNumberInterface> InInterface)
: FSequencerNumericTypeInterface(InInterface, ENumericIntent::Duration)
{}
};
} // namespace Sequencer
} // namespace UE
const FName FSequencer::SelectionDrawerId = TEXT("SelectionDetails");
bool FSequencer::bSelectionLimited = false;
void FSequencer::InitSequencer(const FSequencerInitParams& InitParams
, const TSharedRef<ISequencerObjectChangeListener>& InObjectChangeListener
, const TArray<FOnCreateTrackEditor>& TrackEditorDelegates
, const TArray<FOnCreateEditorObjectBinding>& EditorObjectBindingDelegates
, const TArray<FOnCreateOutlinerColumn>& OutlinerColumnDelegates
, const TArray<FOnCreateOutlinerIndicator>& OutlinerIndicatorDelegates)
{
using namespace UE::MovieScene;
using namespace UE::Sequencer;
const TSharedRef<FSequencer> SharedThisRef = SharedThis(this);
bIsEditingWithinLevelEditor = InitParams.bEditWithinLevelEditor;
ScrubStyle = InitParams.ViewParams.ScrubberStyle;
HostCapabilities = InitParams.HostCapabilities;
CurrentTimeBreadcrumbs = FMovieSceneTransformBreadcrumbs(EMovieSceneBreadcrumbMode::Dense);
SilentModeCount = 0;
bReadOnly = InitParams.ViewParams.bReadOnly;
GetPlaybackSpeeds = InitParams.ViewParams.OnGetPlaybackSpeeds;
const int32 IndexOfOne = GetPlaybackSpeeds.Execute().Find(1.f);
check(IndexOfOne != INDEX_NONE);
CurrentSpeedIndex = IndexOfOne;
SpeedIndexBeforePlay = CurrentSpeedIndex;
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::OnEvaluateSubSequencesInIsolationChanged);
Settings->GetOnShowSelectedNodesOnlyChanged().AddSP(this, &FSequencer::OnSelectedNodesOnlyChanged);
Settings->GetOnTimeDisplayFormatChanged().AddSP(this, &FSequencer::OnTimeDisplayFormatChanged);
const FString TracksSettingsUniqueName = FString::Format(TEXT("{0}.TracksSettings"), { InitParams.ViewParams.UniqueName });
TracksSettings = USequencerSettingsContainer::GetOrCreate<UMovieSceneTracksSettings>(*TracksSettingsUniqueName);
// Initialize numeric type interfaces
{
TAttribute<EFrameNumberDisplayFormats> GetDisplayFormatAttr = MakeAttributeLambda(
[this]
{
return GetSequencerSettings()->GetTimeDisplayFormat();
}
);
TAttribute<uint8> GetZeroPadFramesAttr = MakeAttributeLambda(
[this]()->uint8
{
return GetSequencerSettings()->GetZeroPadFrames();
}
);
TAttribute<FFrameRate> GetTickResolutionAttr = MakeAttributeSP(this, &FSequencer::GetFocusedTickResolution);
TAttribute<FFrameRate> GetDisplayRateAttr = MakeAttributeSP(this, &FSequencer::GetFocusedDisplayRate);
NumericTypeInterfaces.Add(MakeShared<FPositionNumericTypeInterface>(
MakeShared<FFrameNumberInterface>(GetDisplayFormatAttr, GetZeroPadFramesAttr, GetTickResolutionAttr, GetDisplayRateAttr)
)
);
NumericTypeInterfaces.Add(MakeShared<FDurationNumericTypeInterface>(
MakeShared<FFrameNumberInterface>(GetDisplayFormatAttr, GetZeroPadFramesAttr, GetTickResolutionAttr, GetDisplayRateAttr)
)
);
}
ObjectBindingTagCache = MakeUnique<FObjectBindingTagCache>();
{
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([&]
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
State.InvalidateExpiredObjects();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// 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;
}
}
// Update our playback context and client if they were replaced.
// This needs to happen before we resting our object bindings (below) because object binding resolution
// can sometimes try to access the playback context.
if (UObject* OldPlaybackContext = CachedPlaybackContext.Get())
{
if (UObject* const* NewPlaybackContext = ReplacementMap.Find(OldPlaybackContext))
{
CachedPlaybackContext = *NewPlaybackContext;
}
}
if (UObject* OldPlaybackClient = CachedPlaybackClient.GetObject())
{
if (UObject* const* NewPlaybackClient = ReplacementMap.Find(OldPlaybackClient))
{
IMovieScenePlaybackClient* ClientInterface = Cast<IMovieScenePlaybackClient>(*NewPlaybackClient);
CachedPlaybackClient = TWeakInterfacePtr<IMovieScenePlaybackClient>(ClientInterface);
}
}
if (UObject* PlaybackContext = CachedPlaybackContext.Get())
{
if (ReplacementMap.Contains(PlaybackContext->GetClass()))
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllEditorsForAsset(GetRootMovieSceneSequence());
return;
}
}
// Reset Bindings for replaced objects.
bool bAnythingReplaced = false;
for (TPair<UObject*, UObject*> ReplacedObject : ReplacementMap)
{
FGuid Guid = GetHandleToObject(ReplacedObject.Key, false);
if (Guid.IsValid())
{
bAnythingReplaced = true;
}
}
if (bAnythingReplaced)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
State.InvalidateExpiredObjects();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
});
AcquiredResources.Add([=] { FCoreUObjectDelegates::OnObjectsReplaced.Remove(OnObjectsReplacedHandle); });
}
ToolkitHost = InitParams.ToolkitHost;
PlaybackSpeed = 1.f;
PlaybackSpeedBeforePlay = PlaybackSpeed;
ShuttleMultiplier = 0;
ObjectChangeListener = InObjectChangeListener;
PropertyKeyedStatusHandler = MakeShared<FSequencerPropertyKeyedStatusHandler>(SharedThisRef);
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);
Runner = RootTemplateInstance.GetRunner();
RootTemplateInstance.EnableGlobalPreAnimatedStateCapture();
InitialValueCache = UE::MovieScene::FInitialValueCache::GetGlobalInitialValues();
RootTemplateInstance.GetEntitySystemLinker()->AddExtension(InitialValueCache.Get());
InitRootSequenceInstance();
// 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 );
}
}
// OutlinerColumns are registered to be provided to SOutlinerView by SSequencer
for (int32 DelegateIndex = 0; DelegateIndex < OutlinerColumnDelegates.Num(); ++DelegateIndex)
{
check(OutlinerColumnDelegates[DelegateIndex].IsBound());
TSharedRef<IOutlinerColumn> OutlinerColumn = OutlinerColumnDelegates[DelegateIndex].Execute();
if (OutlinerColumn->SupportsSequence(InitParams.RootSequence))
{
check(!OutlinerColumns.Contains(OutlinerColumn->GetColumnName()));
OutlinerColumns.Add(OutlinerColumn->GetColumnName(), OutlinerColumn);
}
}
// OutlinerIndicators are registered to be provided to SOutlinerView by SSequencer
for (int32 DelegateIndex = 0; DelegateIndex < OutlinerIndicatorDelegates.Num(); ++DelegateIndex)
{
check(OutlinerIndicatorDelegates[DelegateIndex].IsBound());
TSharedRef<IOutlinerIndicatorBuilder> OutlinerIndicator = OutlinerIndicatorDelegates[DelegateIndex].Execute();
if (OutlinerIndicator->SupportsSequence(InitParams.RootSequence))
{
check(!OutlinerIndicators.Contains(OutlinerIndicator->GetIndicatorName()));
OutlinerIndicators.Add(OutlinerIndicator->GetIndicatorName(), OutlinerIndicator);
}
}
TWeakPtr<UE::MovieScene::IDeferredSignedObjectChangeHandler> WeakDeferredSignedObjectChangeHandler = UMovieSceneSignedObject::GetDeferredHandler();
if (WeakDeferredSignedObjectChangeHandler.IsValid())
{
DeferredSignedObjectChangeHandler = WeakDeferredSignedObjectChangeHandler.Pin();
}
else
{
DeferredSignedObjectChangeHandler = MakeShared<FDeferredSignedObjectChangeHandler>();
UMovieSceneSignedObject::SetDeferredHandler(DeferredSignedObjectChangeHandler);
}
ViewModel = MakeShared<FSequencerEditorViewModel>(SharedThisRef, GetHostCapabilities());
ViewModel->InitializeEditor();
ViewModel->SetSequence(InitParams.RootSequence);
ViewModel->GetRootSequenceModel()->InitializeExtensions();
ViewModel->GetSelection()->OnChanged.AddRaw(this, &FSequencer::OnSelectionChanged);
FSequencerOutlinerViewModel* OutlinerViewModel = ViewModel->GetOutliner()->CastThisChecked<FSequencerOutlinerViewModel>();
{
OutlinerViewModel->OnGetAddMenuContent = InitParams.ViewParams.OnGetAddMenuContent;
OutlinerViewModel->OnBuildCustomContextMenuForGuid = InitParams.ViewParams.OnBuildCustomContextMenuForGuid;
}
NodeTree->SetRootNode(ViewModel->GetRootModel());
ResetTimeController();
UpdateTimeBases();
PlayPosition.Reset(ConvertFrameTime(GetPlaybackRange().GetLowerBoundValue(), GetRootTickResolution(), PlayPosition.GetInputRate()));
FilterBar = MakeShared<FSequencerFilterBar>(*this);
// Make internal widgets
SequencerWidget = SNew( SSequencer, SharedThis( this ) )
.ViewRange( this, &FSequencer::GetViewRange )
.ClampRange( this, &FSequencer::GetClampRange )
.PlaybackRange( this, &FSequencer::GetPlaybackRange )
.TimeBounds( this, &FSequencer::GetTimeBounds )
.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)
.AreMarkedFramesLocked(this, &FSequencer::AreMarkedFramesLocked)
.OnToggleMarkedFramesLocked(this, &FSequencer::ToggleMarkedFramesLocked)
.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::GetScrubPosition )
.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)
.ShowPlaybackRangeInTimeSlider(InitParams.ViewParams.bShowPlaybackRangeInTimeSlider);
if (GetHostCapabilities().bSupportsCurveEditor)
{
FCurveEditorExtension* CurveEditorExtension = ViewModel->CastDynamic<FCurveEditorExtension>();
check(CurveEditorExtension);
TSharedPtr<FCurveEditor> CurveEditor = CurveEditorExtension->GetCurveEditor();
CurveEditor->OnCurveArrayChanged.AddRaw(this, &FSequencer::OnCurveModelDisplayChanged);
SetShowCurveEditor(GetSequencerSettings()->GetCurveEditorVisible());
}
// When undo occurs, get a notification so we can make sure our view is up to date
GEditor->RegisterForUndo(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(SharedThisRef);
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 );
if (CVarTimeUndo->GetBool())
{
TimeUndoRedoHandler.UndoRedoProxy = NewObject<USequencerTimeChangeUndoRedoProxy>(GetTransientPackage(), NAME_None);
TimeUndoRedoHandler.SetSequencer(SharedThisRef);
TimeUndoRedoHandler.UndoRedoProxy->SetFlags(RF_Transactional | RF_Transient);
}
// 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(nullptr);
AddNodeGroupsCollectionChangedDelegate();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs[0]);
// Selection Details Drawer
if (HostCapabilities.bSupportsSidebar)
{
FSidebarDrawerConfig DetailsDrawer;
DetailsDrawer.UniqueId = SelectionDrawerId;
DetailsDrawer.ButtonText = LOCTEXT("SelectionDetailsPanelLabel", "Selection");
DetailsDrawer.ToolTipText = TAttribute<FText>::CreateSP(this, &FSequencer::GetSidebarSelectionDrawerToolTipText);
DetailsDrawer.Icon = FAppStyle::GetBrush(TEXT("EditorPreferences.TabIcon"));
DetailsDrawer.InitialState = Settings->GetSidebarState().FindOrAddDrawerState(SelectionDrawerId);
RegisterDrawer(MoveTemp(DetailsDrawer));
RegisterDrawerSection(SelectionDrawerId, MakeShared<FSequencerSelectionDrawer>(SharedThisRef));
}
}
void FSequencer::SetSequencerSettings(USequencerSettings* InSettings)
{
if (Settings)
{
Settings->GetOnEvaluateSubSequencesInIsolationChanged().RemoveAll(this);
Settings->GetOnShowSelectedNodesOnlyChanged().RemoveAll(this);
Settings->GetOnTimeDisplayFormatChanged().RemoveAll(this);
}
Settings = InSettings;
Settings->GetOnEvaluateSubSequencesInIsolationChanged().AddSP(this, &FSequencer::RestorePreAnimatedState);
Settings->GetOnShowSelectedNodesOnlyChanged().AddSP(this, &FSequencer::OnSelectedNodesOnlyChanged);
Settings->GetOnTimeDisplayFormatChanged().AddSP(this, &FSequencer::OnTimeDisplayFormatChanged);
OnTimeDisplayFormatChanged();
}
void FSequencer::OnPlaybackContextChanged()
{
RootTemplateInstance.PlaybackContextChanged(*this);
InitRootSequenceInstance();
}
void FSequencer::OnEvaluateSubSequencesInIsolationChanged()
{
RestorePreAnimatedState();
FMovieSceneSequenceID NewOverrideRoot = Settings->ShouldEvaluateSubSequencesInIsolation() ? ActiveTemplateIDs.Top() : MovieSceneSequenceID::Root;
UMovieSceneEntitySystemLinker* Linker = RootTemplateInstance.GetEntitySystemLinker();
RootTemplateInstance.FindInstance(MovieSceneSequenceID::Root)->OverrideRootSequence(NewOverrideRoot);
ForceEvaluate();
}
void FSequencer::InitRootSequenceInstance()
{
using namespace UE::MovieScene;
// Add the camera cut playback capability.
TSharedPtr<FSharedPlaybackState> SharedPlaybackState = RootTemplateInstance.GetSharedPlaybackState();
if (ensure(SharedPlaybackState.IsValid()))
{
SharedPlaybackState->SetOrAddCapabilityRaw<FCameraCutPlaybackCapability>((FCameraCutPlaybackCapability*)this);
SharedPlaybackState->SetOrAddCapabilityRaw<FCameraCutViewTargetCacheCapability>((FCameraCutViewTargetCacheCapability*)this);
}
}
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 )
, bPerspectiveViewportPossessionEnabled( true )
, bPerspectiveViewportCameraCutEnabled( false )
, bIsEditingWithinLevelEditor( false )
, bNeedTreeRefresh( false )
, NodeTree( MakeShareable( new FSequencerNodeTree( *this ) ) )
, bUpdatingSequencerSelection( false )
, bUpdatingExternalSelection( false )
, bNeedsEvaluate(false)
, bNeedsInvalidateCachedData(false)
, TimeControllerState(ETimeControllerState::ReadyToPlay)
{
// 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);
TimeOperationDomain = UE::Sequencer::ETimeDomain::Warped;
}
FSequencer::~FSequencer()
{
if (Runner)
{
Runner->QueueFinalUpdate(RootTemplateInstance.GetRootInstanceHandle());
Runner->Flush();
}
if (GEditor)
{
GEditor->UnregisterForUndo(this);
}
for (auto TrackEditor : TrackEditors)
{
TrackEditor->OnRelease();
}
AcquiredResources.Release();
if (ViewModel && ViewModel->GetSelection())
{
ViewModel->GetSelection()->OnChanged.RemoveAll(this);
}
ViewModel.Reset();
SequencerWidget.Reset();
TrackEditors.Empty();
FilterBar.Reset();
RootTemplateInstance.TearDown();
}
void FSequencer::Close()
{
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC != nullptr)
{
LevelVC->ViewModifiers.RemoveAll(this);
}
}
if (OldMaxTickRate.IsSet())
{
GEngine->SetMaxFPS(OldMaxTickRate.GetValue());
OldMaxTickRate.Reset();
}
if (Runner)
{
Runner->QueueFinalUpdate(RootTemplateInstance.GetRootInstanceHandle());
Runner->Flush();
}
// Save the user's preference for curve editor visibility since it can be closed outside of Sequencer's control (ie. close tab)
if (GetHostCapabilities().bSupportsCurveEditor)
{
GetSequencerSettings()->SetCurveEditorVisible(GetCurveEditorIsVisible());
}
RestorePreAnimatedState();
for (auto TrackEditor : TrackEditors)
{
TrackEditor->OnRelease();
}
SequencerWidget.Reset();
TrackEditors.Empty();
FilterBar.Reset();
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;
}
}
}
ensureAlwaysMsgf(TrackEditor, TEXT("Unable to find a track editor for track type %s"), *TrackClass->GetName());
return TrackEditor;
}
void FSequencer::Tick(float InDeltaTime)
{
using namespace UE::MovieScene;
using namespace UE::Sequencer;
static bool bEnableRefCountCheck = true;
if (!FSlateApplication::Get().AnyMenusVisible())
{
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();
}
{
FViewModelHierarchyOperation Operation(GetViewModel()->GetRootSequenceModel()->GetSharedData());
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);
UpdateCachedPlaybackContextAndClient();
{
if (CompiledDataManager->IsDirty(RootSequencePtr))
{
// Try and preserve the current local time across compilations.
// When a modification results in a change of transform or time-warp,
// this helps to stop the play head from jumping around
FMovieSceneSequenceTransform CachedRootToUnwarpedLocalTransform = RootToUnwarpedLocalTransform;
FFrameTime OldLocalTime = GetUnwarpedLocalTime().Time;
CompiledDataManager->Compile(RootSequencePtr);
// 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 (!GetFocusedMovieSceneSequence() || !GetFocusedMovieSceneSequence()->GetMovieScene())
{
PopToSequenceInstance(MovieSceneSequenceID::Root);
}
// 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;
}
}
if (CachedRootToUnwarpedLocalTransform != RootToUnwarpedLocalTransform)
{
FTimeDomainOverride TimeDomain = OverrideTimeDomain(ETimeDomain::Unwarped);
SetLocalTime(OldLocalTime, ESnapTimeMode::STM_None, false /* bEvaluate */);
}
SuppressAutoEvalSignature.Reset();
for (TSharedRef<UE::Sequencer::FSequencerNumericTypeInterface> Interface : GetNumericTypeInterfaces())
{
if (Interface->Interface->GetOnSettingChanged() != nullptr)
{
Interface->Interface->GetOnSettingChanged()->Broadcast();
}
}
}
}
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()->CinematicTimeDilation : 1.f;
if (TimeControllerState == ETimeControllerState::PreparingToPlay && TimeController->IsReadyToPlay())
{
SetPlaybackStatus(EMovieScenePlayerStatus::Playing);
TimeControllerState = ETimeControllerState::ReadyToPlay;
}
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, CurrentTimeBreadcrumbs);
}
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 clamped local space
FMovieSceneTransformBreadcrumbs Breadcrumbs;
FFrameTime LocalTime = RootToUnwarpedLocalTransform.TransformTime(NewGlobalTime, FTransformTimeParams().HarvestBreadcrumbs(Breadcrumbs).IgnoreClamps());
FTimeDomainOverride DomainOverride = OverrideTimeDomain(ETimeDomain::Unwarped);
SetLocalTimeLooped(LocalTime, Breadcrumbs);
if (GetPlaybackStatus() == EMovieScenePlayerStatus::Playing)
{
const float ThresholdPercentage = 0.15f;
UpdateAutoScroll(GetLocalTime().Time / GetFocusedTickResolution(), ThresholdPercentage);
}
}
else
{
PlayPosition.Reset(GetGlobalTime().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();
}
}
if (PendingScrubPosition.IsSet())
{
const bool bEvaluate = false;
FTimeDomainOverride DomainOverride = OverrideTimeDomain(PendingScrubPosition->Get<1>());
SetLocalTimeDirectly(PendingScrubPosition->Get<0>(), bEvaluate);
PendingScrubPosition.Reset();
bNeedsEvaluate = true;
}
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::GetLocalTimeWarpTransform() const
{
return LocalToWarpedLocalTransform;
}
FMovieSceneSequenceTransform FSequencer::GetGlobalPlaybackWarpTransform() const
{
return GlobalPlaybackWarpTransform;
}
FMovieSceneSequenceTransform FSequencer::GetFocusedMovieSceneSequenceTransform() const
{
return RootToWarpedLocalTransform;
}
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)
{
using namespace UE::MovieScene;
RemoveNodeGroupsCollectionChangedDelegate();
const UMovieSceneSequence* PreviousRootSequence = RootSequence.Get();
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);
}
}
}
if (Runner)
{
Runner->QueueFinalUpdate(RootTemplateInstance.GetRootInstanceHandle());
Runner->Flush();
}
ActiveTemplateIDs.Reset();
ActiveTemplateIDs.Add(MovieSceneSequenceID::Root);
ActiveTemplateStates.Reset();
ActiveTemplateStates.Add(true);
RootTemplateInstance.Initialize(NewSequence, *this, CompiledDataManager);
RootTemplateInstance.EnableGlobalPreAnimatedStateCapture();
UpdateSubSequenceData();
CurrentTimeBreadcrumbs.Reset();
ResetPerMovieSceneData();
SequencerWidget->ResetBreadcrumbs();
PlayPosition.Reset(ConvertFrameTime(GetPlaybackRange().GetLowerBoundValue(), GetRootTickResolution(), PlayPosition.GetInputRate()));
TimeController->Reset(GetGlobalTime());
UpdateSequencerCustomizations(PreviousRootSequence);
AddNodeGroupsCollectionChangedDelegate();
InitRootSequenceInstance();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
}
void FSequencer::FocusSequenceInstance(UMovieSceneSubSection& InSubSection)
{
RemoveNodeGroupsCollectionChangedDelegate();
const UMovieSceneSequence* PreviousFocusedSequence = GetFocusedMovieSceneSequence();
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(ActiveTemplateIDs.Top());
}
UpdateSubSequenceData();
UpdateSequencerCustomizations(PreviousFocusedSequence);
ScrubPositionParent.Reset();
// Always reset the hotspot so that it can be regenerated when rebuilding
ViewModel->GetTrackArea()->SetHotspot(nullptr);
// Reset data that is only used for the previous movie scene
ResetPerMovieSceneData();
SequencerWidget->UpdateBreadcrumbs();
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (!State.FindSequence(SequenceID))
{
State.AssignSequence(SequenceID, *FocusedSequence, *this);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// 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();
if (Settings->ShouldResetPlayheadWhenNavigating())
{
FQualifiedFrameTime CurrentTime = GetLocalTime();
if (!SubSequenceRange.Contains(CurrentTime.Time.FloorToFrame()))
{
SetLocalTime(SubSequenceRange.GetLowerBoundValue(), ESnapTimeMode::STM_None);
}
}
bNeedsEvaluate = true;
bGlobalMarkedFramesCached = false;
}
TSharedPtr<UE::Sequencer::FSequencerEditorViewModel> FSequencer::GetViewModel() const
{
return ViewModel;
}
void FSequencer::SuppressAutoEvaluation(UMovieSceneSequence* Sequence, const FGuid& InSequenceSignature)
{
SuppressAutoEvalSignature = MakeTuple(MakeWeakObjectPtr(Sequence), InSequenceSignature);
}
FGuid FSequencer::CreateBinding(UObject& InObject, const UE::Sequencer::FCreateBindingParams& InParams)
{
return FSequencerUtilities::CreateBinding(AsShared(), InObject, InParams);
}
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;
SelectedItems.Reserve(ViewModel->GetSelection()->Outliner.Num());
for (FViewModelPtr SelectedItem : ViewModel->GetSelection()->Outliner)
{
SelectedItems.Add(SelectedItem.AsModel().ToSharedRef());
}
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));
}
FSequencerKeyCollection* FSequencer::GetKeyCollection()
{
if (!SelectedKeyCollection.IsValid())
{
SelectedKeyCollection.Reset(new FSequencerKeyCollection);
}
return SelectedKeyCollection.Get();
}
void FSequencer::GetAllKeys(TUniquePtr<FSequencerKeyCollection>& KeyCollection, float DuplicateThresholdSeconds) const
{
using namespace UE::Sequencer;
if (!KeyCollection.IsValid())
{
KeyCollection.Reset(new FSequencerKeyCollection);
}
const FFrameNumber ThresholdFrames = (DuplicateThresholdSeconds * GetFocusedTickResolution()).FloorToFrame();
KeyCollection->Update(FSequencerKeyCollectionSignature::FromNodesRecursive({NodeTree->GetRootNode()->AsShared()}, ThresholdFrames));
}
void FSequencer::PopToSequenceInstance(FMovieSceneSequenceIDRef SequenceID)
{
if( ActiveTemplateIDs.Num() > 1 )
{
TemplateIDBackwardStack.Push(ActiveTemplateIDs.Top());
TemplateIDForwardStack.Reset();
RemoveNodeGroupsCollectionChangedDelegate();
const UMovieSceneSequence* PreviousFocusedSequence = GetFocusedMovieSceneSequence();
// 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(ActiveTemplateIDs.Top());
}
UpdateSequencerCustomizations(PreviousFocusedSequence);
AddNodeGroupsCollectionChangedDelegate();
ScrubPositionParent.Reset();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
bNeedsEvaluate = true;
bGlobalMarkedFramesCached = false;
}
}
void FSequencer::UpdateSubSequenceData()
{
using namespace UE::MovieScene;
SubSequenceRange = TRange<FFrameNumber>::Empty();
const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(RootTemplateInstance.GetCompiledDataID());
// If the root transform for the whole sequence is in the Play Rate domain, we assign a GlobalPlaybackWarpTransform
// that mutates the evaluation ranges as we evaluate. This is not necessary for Time domain transforms since that is handled
// by the sequence updaters themselves
FMovieSceneSequenceTransform RootTransform = Hierarchy ? Hierarchy->GetRootTransform() : FMovieSceneSequenceTransform();;
if (Hierarchy && Hierarchy->GetRootTransform().FindFirstWarpDomain() == ETimeWarpChannelDomain::PlayRate)
{
GlobalPlaybackWarpTransform = Hierarchy->GetRootTransform();
}
else
{
GlobalPlaybackWarpTransform = FMovieSceneSequenceTransform();
}
// Find the parent sub section and set up the sub sequence range, if necessary
if (ActiveTemplateIDs.Num() <= 1)
{
// Ensure in this case we also reset the CurrentTimeBreadcrumbs.
CurrentTimeBreadcrumbs.Reset();
// Reset everything first
RootToUnwarpedLocalTransform = RootToWarpedLocalTransform = LocalToWarpedLocalTransform = FMovieSceneSequenceTransform();
if (Hierarchy)
{
LocalToWarpedLocalTransform = Hierarchy->GetRootTransform();
}
RootToWarpedLocalTransform = LocalToWarpedLocalTransform;
return;
}
check(Hierarchy);
const FMovieSceneSubSequenceData* SubSequenceData = Hierarchy->FindSubData(ActiveTemplateIDs.Top());
if (SubSequenceData)
{
SubSequenceRange = SubSequenceData->PlayRange.Value;
RootToUnwarpedLocalTransform = SubSequenceData->RootToUnwarpedLocalTransform * RootTransform;
RootToWarpedLocalTransform = SubSequenceData->RootToSequenceTransform * RootTransform;
LocalToWarpedLocalTransform = SubSequenceData->LocalToWarpedLocalTransform;
const bool bIsScrubbing = GetPlaybackStatus() == EMovieScenePlayerStatus::Scrubbing;
FFrameTime CurrentTime = GetGlobalTime().Time;
CurrentTimeBreadcrumbs.Reset();
RootToWarpedLocalTransform.TransformTime(CurrentTime, FTransformTimeParams().HarvestBreadcrumbs(CurrentTimeBreadcrumbs).IgnoreClamps());
// Inner play range in unwarped space
UMovieSceneSequence* SubSequence = SubSequenceData->GetSequence();
TRange<FFrameNumber> PlaybackRange = SubSequence->GetMovieScene()->GetPlaybackRange();
FMovieSceneInverseSequenceTransform LocalToRootTransform = RootToUnwarpedLocalTransform.Inverse();
const int32 PlaybackSize = UE::MovieScene::DiscreteSize(PlaybackRange);
TOptional<FFrameTime> PlayStart = LocalToRootTransform.TryTransformTime(PlaybackRange.GetLowerBoundValue(), CurrentTimeBreadcrumbs, EInverseEvaluateFlags::Backwards | EInverseEvaluateFlags::Cycle);
TOptional<FFrameTime> PlayEnd = LocalToRootTransform.TryTransformTime(PlaybackRange.GetUpperBoundValue(), CurrentTimeBreadcrumbs, EInverseEvaluateFlags::Forwards | EInverseEvaluateFlags::Cycle);
TRange<FFrameTime> ValidRange = TRange<FFrameTime>::All();
if (PlayStart)
{
ValidRange.SetLowerBound(TRangeBound<FFrameTime>::Inclusive(PlayStart.GetValue()));
}
if (PlayEnd)
{
ValidRange.SetUpperBound(TRangeBound<FFrameTime>::Inclusive(PlayEnd.GetValue()));
}
for (int32 Index = 0; Index < ActiveTemplateIDs.Num(); ++Index)
{
FMovieSceneSequenceID SequenceID = ActiveTemplateIDs[Index];
const FMovieSceneSubSequenceData* SubData = Hierarchy->FindSubData(SequenceID);
if (SubData)
{
// Clamp to the sub section range recursively
if (!SubData->ParentPlayRange.Value.Contains(CurrentTime.FrameNumber))
{
FFrameNumber Lower = DiscreteInclusiveLower(SubData->ParentPlayRange.Value);
FFrameNumber Upper = DiscreteExclusiveUpper(SubData->ParentPlayRange.Value);
if (CurrentTime.FrameNumber < Lower)
{
CurrentTime = FFrameTime(Lower);
}
else if (CurrentTime.FrameNumber >= Upper)
{
CurrentTime = FFrameTime(Upper - 1, FFrameTime::MaxSubframe);
}
}
CurrentTime = SubData->OuterToInnerTransform.TransformTime(CurrentTime);
// Clamp the range and transform it into inner space
ValidRange = TRange<FFrameTime>::Intersection(ValidRange, ConvertToFrameTimeRange(SubData->ParentPlayRange.Value));
if (ValidRange.IsEmpty())
{
break;
}
ValidRange = SubData->OuterToInnerTransform.ComputeTraversedHull(ValidRange);
}
}
SubSequenceRange = ConvertToDiscreteRange(ValidRange);
}
else
{
// Ensure in this case we also reset the CurrentTimeBreadcrumbs.
CurrentTimeBreadcrumbs.Reset();
}
}
void FSequencer::UpdateSequencerCustomizations(const UMovieSceneSequence* PreviousFocusedSequence)
{
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
check(FocusedSequence != nullptr);
ViewModel->UpdateSequencerCustomizations(PreviousFocusedSequence);
TArrayView<const FSequencerCustomizationInfo> CustomizationInfos = ViewModel->GetActiveCustomizationInfos();
// Apply customizations to our editor.
SequencerWidget->ApplySequencerCustomizations(CustomizationInfos);
// Apply customizations to ourselves.
OnPaste.Reset();
for (const FSequencerCustomizationInfo& CustomizationInfo : CustomizationInfos)
{
if (CustomizationInfo.OnPaste.IsBound())
{
OnPaste.Add(CustomizationInfo.OnPaste);
}
}
// TODO: Allow customization of which custom bindings are allowed? For now just iterate over all subclasses and sort by priority
// Cache custom spawnable types
RefreshSupportedCustomBindingTypes();
}
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();
}
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
for (TPair<FMovieSceneSequenceID, FGuid> BoundGuid : BoundGuids)
{
State.Invalidate(BoundGuid.Value, BoundGuid.Key);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
void FSequencer::GetConstructionScriptActors(UMovieScene* MovieScene, FMovieSceneSequenceIDRef SequenceID, TSet<TWeakObjectPtr<AActor> >& BoundActors, TArray < TPair<FMovieSceneSequenceID, FGuid> >& BoundGuids)
{
auto ShouldRerunConstructionScriptsForClass = [](UClass* Class)
{
// If any blueprints in the class hierarchy are marked as bRunConstructionScriptInSequencer, we need to rerun them
while (Class)
{
UBlueprint* Blueprint = Cast<UBlueprint>(Class->ClassGeneratedBy);
if (!Blueprint)
{
// As soon as we find a non BP class, we can't re-run construction scripts any more
return false;
}
else if (Blueprint->bRunConstructionScriptInSequencer)
{
return true;
}
Class = Class->GetSuperClass();
}
return false;
};
for (int32 Index = 0; Index < MovieScene->GetPossessableCount(); ++Index)
{
FGuid ThisGuid = MovieScene->GetPossessable(Index).GetGuid();
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(ThisGuid, SequenceID))
{
AActor* Actor = Cast<AActor>(WeakObject.Get());
if (Actor && ShouldRerunConstructionScriptsForClass(Actor->GetClass()))
{
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))
{
AActor* Actor = Cast<AActor>(WeakObject.Get());
if (Actor && ShouldRerunConstructionScriptsForClass(Actor->GetClass()))
{
BoundActors.Add(Actor);
BoundGuids.Add(TPair<FMovieSceneSequenceID, FGuid>(SequenceID, ThisGuid));
}
}
}
}
void FSequencer::DeleteSections(const TSet<UMovieSceneSection*>& Sections)
{
if (IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
bool bAnythingRemoved = false;
FScopedTransaction DeleteSectionTransaction( NSLOCTEXT("Sequencer", "DeleteSection_Transaction", "Delete Section") );
for (UMovieSceneSection* Section : Sections)
{
if (!Section || 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 );
}
ViewModel->GetSelection()->TrackArea.Empty();
}
void FSequencer::DeleteSelectedKeys()
{
using namespace UE::Sequencer;
const bool bUseParallel = true;
FScopedTransaction DeleteKeysTransaction( NSLOCTEXT("Sequencer", "DeleteSelectedKeys_Transaction", "Delete Selected Keys") );
bool bAnythingRemoved = false;
FSelectedKeysByChannel KeysByChannel(ViewModel->GetSelection()->KeySelection);
TSet<UObject*> ModifiedObjects;
FCriticalSection Mutex;
ParallelFor(KeysByChannel.SelectedChannels.Num(), [this, &KeysByChannel, &Mutex, &ModifiedObjects, &bAnythingRemoved](int32 Index)
{
const FSelectedChannelInfo& ChannelInfo = KeysByChannel.SelectedChannels[Index];
if (ChannelInfo.OwningSection->IsReadOnly())
{
return;
}
if (ChannelInfo.KeyHandles.Num() == 0)
{
return;
}
TSharedPtr<FChannelModel> Channel = ViewModel->GetSelection()->KeySelection.GetModelForKey(ChannelInfo.KeyHandles[0]);
if (!Channel)
{
return;
}
TSharedPtr<IKeyArea> KeyArea = Channel->GetKeyArea();
if (!KeyArea)
{
return;
}
UObject* Owner = nullptr;
const FMovieSceneChannelMetaData* ChannelMetaData = ChannelInfo.Channel.GetMetaData();
if (ChannelMetaData)
{
Owner = ChannelMetaData->WeakOwningObject.Get();
}
if (!Owner)
{
Owner = ChannelInfo.OwningSection;
}
{
FScopeLock Lock(&Mutex);
if (!ModifiedObjects.Contains(Owner))
{
Owner->Modify();
ModifiedObjects.Add(ChannelInfo.OwningSection);
}
bAnythingRemoved = true;
}
KeyArea->DeleteKeys(ChannelInfo.KeyHandles, GetLocalTime().Time.FloorToFrame());
},bUseParallel ? EParallelForFlags::None : EParallelForFlags::ForceSingleThread);
if (bAnythingRemoved)
{
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::TrackValueChanged );
ViewModel->GetSelection()->KeySelection.Empty();
}
}
void FSequencer::SetInterpTangentMode(ERichCurveInterpMode InterpMode, ERichCurveTangentMode TangentMode)
{
if (ViewModel->GetSelection()->KeySelection.Num() == 0)
{
return;
}
FScopedTransaction SetInterpTangentModeTransaction(NSLOCTEXT("Sequencer", "SetInterpTangentMode_Transaction", "Set Interpolation and Tangent Mode"));
bool bAnythingChanged = false;
FSelectedKeysByChannel KeysByChannel(ViewModel->GetSelection()->KeySelection);
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
if (ViewModel->GetSelection()->KeySelection.Num() == 0)
{
return;
}
FScopedTransaction SetInterpTangentWeightModeTransaction(NSLOCTEXT("Sequencer", "ToggleInterpTangentWeightMode_Transaction", "Toggle Tangent Weight Mode"));
bool bAnythingChanged = false;
FSelectedKeysByChannel KeysByChannel(ViewModel->GetSelection()->KeySelection);
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(ViewModel->GetSelection()->KeySelection);
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 = ViewModel->GetSelection()->KeySelection.Num() > 0;
return bKeysSelected;
}
void FSequencer::TransformSelectedKeysAndSections(FFrameTime InDeltaTime, float InScale)
{
using namespace UE::Sequencer;
FScopedTransaction TransformKeysAndSectionsTransaction(NSLOCTEXT("Sequencer", "TransformKeysandSections_Transaction", "Transform Keys and Sections"));
bool bAnythingChanged = false;
TSet<UMovieSceneSection*> SelectedSections = ViewModel->GetSelection()->GetSelectedSections();
const FFrameTime OriginTime = GetLocalTime().Time;
FSelectedKeysByChannel KeysByChannel(ViewModel->GetSelection()->KeySelection);
TMap<UMovieSceneSection*, TRange<FFrameNumber>> SectionToNewBounds;
if (InScale != 0.f)
{
TMap<FMovieSceneChannel*, TPair<TArray<FFrameNumber>, TArray<FKeyHandle>>> ChannelsAndKeyTimes;
// 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 (SelectedSections.Contains(ChannelInfo.OwningSection))
{
continue;
}
// Skip any locked sections
if (ChannelInfo.OwningSection->IsLocked())
{
continue;
}
TPair<TArray<FFrameNumber>, TArray<FKeyHandle>>& KeyTimesScratch = ChannelsAndKeyTimes.FindOrAdd(Channel);
const int32 NumKeys = ChannelInfo.KeyHandles.Num();
KeyTimesScratch.Key.Reset(NumKeys);
KeyTimesScratch.Key.SetNum(NumKeys);
// Populate the key times scratch buffer with the times for these handles
KeyTimesScratch.Value = ChannelInfo.KeyHandles;
Channel->GetKeyTimes(ChannelInfo.KeyHandles, KeyTimesScratch.Key);
// 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.Key[0];
FFrameTime HighestFrameTime = KeyTimesScratch.Key[0];
// Perform the transformation
for (FFrameNumber& Time : KeyTimesScratch.Key)
{
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));
bAnythingChanged = true;
}
}
// set key times AFTER getting where we want all of them where we want to go, this way we avoid double transforms
// on things like smart keys for spaces and constraints.
for (TPair<FMovieSceneChannel*, TPair<TArray<FFrameNumber>, TArray<FKeyHandle>>>& ChannelAndKeys : ChannelsAndKeyTimes)
{
// Apply the new, transformed key times
// Channel->SetKeyTimes(KeyHandles,KeyTimes) -->
ChannelAndKeys.Key->SetKeyTimes(ChannelAndKeys.Value.Value, ChannelAndKeys.Value.Key);
}
// Dilate the sections
for (UMovieSceneSection* Section : SelectedSections)
{
// Skip any locked sections
if (Section->IsLocked())
{
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);
}
}
}
// Marked frames
const FMarkedFrameSelection& SelectedMarkedFrames = ViewModel->GetSelection()->MarkedFrames;
if (SelectedMarkedFrames.Num() > 0)
{
bAnythingChanged = true;
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
FocusedMovieScene->Modify();
for (TSet<int32>::TConstIterator It = SelectedMarkedFrames.GetSelected(); It; ++It)
{
const int32 MarkIndex = *It;
FFrameNumber FrameNumber = FocusedMovieScene->GetMarkedFrames()[MarkIndex].FrameNumber;
FrameNumber = (OriginTime + InDeltaTime + (FrameNumber - OriginTime) * InScale).FloorToFrame();
FocusedMovieScene->SetMarkedFrame(MarkIndex, FrameNumber);
}
FocusedMovieScene->SortMarkedFrames();
}
}
// 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)
{
if (IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
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);
ViewModel->GetSelection()->Empty(); //todo restore key and section selection
}
void FSequencer::ShrinkTime(FFrameTime InDeltaTime)
{
using namespace UE::Sequencer;
// 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);
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
if (Selection->KeySelection.Num() > 0)
{
FNotificationInfo Info(FText::Format(NSLOCTEXT("Sequencer", "ShrinkTimeFailedKeys", "Shrink failed. There are {0} keys in between"), Selection->KeySelection.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)
{
FScopedTransaction SetKeyTransaction(NSLOCTEXT("Sequencer", "SetTransformKey_Transaction", "Set Transform Key"));
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;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
State.ClearObjectCaches(*this);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
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();
}
}
void FSequencer::RecreateCurveEditor()
{
using namespace UE::Sequencer;
FCurveEditorIntegrationExtension* CurveEditorIntegration= ViewModel->GetRootModel()->CastDynamic<FCurveEditorIntegrationExtension>();
if (ensure(CurveEditorIntegration))
{
CurveEditorIntegration->ResetCurveEditor();
if (GetSequencerSettings()->ShouldSyncCurveEditorSelection())
{
FCurveEditorExtension* CurveEditorExtension = ViewModel->CastDynamic<FCurveEditorExtension>();
if (ensure(CurveEditorExtension))
{
return CurveEditorExtension->RequestSyncSelection();
}
}
}
}
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
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return TRange<FFrameNumber>();
}
return FocusedMovieScene->GetSelectionRange();
}
void FSequencer::SetSelectionRange(TRange<FFrameNumber> Range)
{
UMovieScene* FocusedMovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
const bool bInitiallyEmpty = GetSelectionRange().IsEmpty();
const FScopedTransaction Transaction(LOCTEXT("SetSelectionRange_Transaction", "Set Selection Range"));
FocusedMovieScene->Modify();
FocusedMovieScene->SetSelectionRange(Range);
if (bInitiallyEmpty && GetLoopMode() != ESequencerLoopMode::SLM_LoopSelectionRange)
{
Settings->SetLoopMode(ESequencerLoopMode::SLM_LoopSelectionRange);
}
}
void FSequencer::SetSelectionRangeEnd(FFrameTime EndFrame)
{
using namespace UE::MovieScene;
const bool bInitiallyEmpty = GetSelectionRange().IsEmpty();
const FFrameNumber LocalTime = EndFrame.FrameNumber;
const FFrameNumber StartFrame = (bInitiallyEmpty || GetSelectionRange().GetLowerBoundValue() >= LocalTime)
? DiscreteInclusiveLower(GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange())
: GetSelectionRange().GetLowerBoundValue();
if (StartFrame >= LocalTime)
{
SetSelectionRange(TRange<FFrameNumber>(LocalTime - 1, LocalTime));
}
else
{
SetSelectionRange(TRange<FFrameNumber>(StartFrame, LocalTime));
}
if (bInitiallyEmpty && GetLoopMode() != ESequencerLoopMode::SLM_LoopSelectionRange)
{
Settings->SetLoopMode(ESequencerLoopMode::SLM_LoopSelectionRange);
}
}
void FSequencer::SetSelectionRangeStart(FFrameTime StartFrame)
{
using namespace UE::MovieScene;
const bool bInitiallyEmpty = GetSelectionRange().IsEmpty();
const FFrameNumber LocalTime = StartFrame.FrameNumber;
const FFrameNumber EndFrame = (bInitiallyEmpty || GetSelectionRange().GetUpperBoundValue() <= LocalTime)
? DiscreteExclusiveUpper(GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange())
: GetSelectionRange().GetUpperBoundValue();
if (EndFrame <= LocalTime)
{
SetSelectionRange(TRange<FFrameNumber>(LocalTime, LocalTime + 1));
}
else
{
SetSelectionRange(TRange<FFrameNumber>(LocalTime, EndFrame));
}
if (bInitiallyEmpty && GetLoopMode() != ESequencerLoopMode::SLM_LoopSelectionRange)
{
Settings->SetLoopMode(ESequencerLoopMode::SLM_LoopSelectionRange);
}
}
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;
}
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
TSet<UMovieSceneSection*> SectionsWithKeys;
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);
if (HandlesScratch.Num() > 0)
{
SectionsWithKeys.Add(Section);
}
for (int32 Index = 0; Index < HandlesScratch.Num(); ++Index)
{
Selection->KeySelection.Select(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())
{
UMovieSceneSection* Section = SectionModel->GetSection();
if (!SectionsWithKeys.Contains(Section))
{
Selection->TrackArea.Select(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();
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
// Don't empty all selection, just keys and sections
Selection->KeySelection.Empty();
Selection->TrackArea.Empty();
for (const TViewModelPtr<IOutlinerExtension>& DisplayNode : NodeTree->GetRootNodes())
{
SelectInSelectionRange(DisplayNode, SelectionRange, bSelectKeys, bSelectSections);
}
}
void FSequencer::SelectForward()
{
using namespace UE::Sequencer;
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameNumber CurrentFrame = GetLocalTime().ConvertTo(TickResolution).CeilToFrame();
TRange<FFrameNumber> SelectionRange(CurrentFrame, TNumericLimits<FFrameNumber>::Max());
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
Selection->KeySelection.Empty();
Selection->TrackArea.Empty();
if (Selection->GetNodesWithSelectedKeysOrSections().Num() != 0)
{
for (TViewModelPtr<IOutlinerExtension> Item : Selection->IterateIndirectOutlinerSelection())
{
SelectInSelectionRange(Item, SelectionRange, true, true);
}
}
else if (Selection->Outliner.Num() != 0)
{
for (TViewModelPtr<IOutlinerExtension> Item : Selection->Outliner)
{
SelectInSelectionRange(Item, SelectionRange, true, true);
}
}
else
{
SelectInSelectionRange(ViewModel->GetRootModel(), SelectionRange, true, true);
}
}
void FSequencer::SelectBackward()
{
using namespace UE::Sequencer;
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameNumber CurrentFrame = GetLocalTime().ConvertTo(TickResolution).CeilToFrame();
TRange<FFrameNumber> SelectionRange(TNumericLimits<FFrameNumber>::Min(), CurrentFrame);
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
Selection->KeySelection.Empty();
Selection->TrackArea.Empty();
if (Selection->GetNodesWithSelectedKeysOrSections().Num() != 0)
{
for (TViewModelPtr<IOutlinerExtension> Item : Selection->IterateIndirectOutlinerSelection())
{
SelectInSelectionRange(Item, SelectionRange, true, true);
}
}
else if (Selection->Outliner.Num() != 0)
{
for (TViewModelPtr<IOutlinerExtension> Item : Selection->Outliner)
{
SelectInSelectionRange(Item, SelectionRange, true, true);
}
}
else
{
SelectInSelectionRange(ViewModel->GetRootModel(), SelectionRange, true, true);
}
}
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);
}
}
}
}
void FSequencer::SetSelectionRangeToShot(const bool bNextShot)
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = MovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack)
{
return;
}
UMovieSceneSection* TargetShotSection = MovieSceneHelpers::FindNextSection(CinematicShotTrack->GetAllSections(), GetLocalTime().Time.FloorToFrame());
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->FindTrack(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::FocusPlaybackTime()
{
const double CurrentTime = GetLocalTime().AsSeconds();
TRange<double> NewViewRange = GetViewRange();
double MidRange = (NewViewRange.GetUpperBoundValue() - NewViewRange.GetLowerBoundValue()) / 2.0 + NewViewRange.GetLowerBoundValue();
NewViewRange.SetLowerBoundValue(NewViewRange.GetLowerBoundValue() - (MidRange - CurrentTime));
NewViewRange.SetUpperBoundValue(NewViewRange.GetUpperBoundValue() - (MidRange - CurrentTime));
SetViewRange(NewViewRange, EViewRangeInterpolation::Animated);
}
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())
{
// Playback ranges should always have exclusive upper bounds
if (NewBounds.GetUpperBound().IsInclusive())
{
NewBounds.SetUpperBound(TRangeBound<FFrameNumber>::Exclusive(NewBounds.GetUpperBound().GetValue() + 1));
}
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
{
using namespace UE::MovieScene;
const FFrameRate FocusedResolution = GetFocusedTickResolution();
FQualifiedFrameTime CurrentPosition = GetGlobalTime();
CurrentPosition.Time = RootToWarpedLocalTransform.TransformTime(CurrentPosition.Time, FTransformTimeParams().IgnoreClamps());
return CurrentPosition;
}
TOptional<int32> FSequencer::GetLocalLoopIndex() const
{
using namespace UE::MovieScene;
TOptional<int32> LoopIndex;
RootToUnwarpedLocalTransform.TransformTime(GetGlobalTime().Time, FTransformTimeParams().TrackCycleCounts(&LoopIndex));
return LoopIndex;
}
FQualifiedFrameTime FSequencer::GetGlobalTime() const
{
FFrameTime RootTime = ConvertFrameTime(PlayPosition.GetCurrentPosition(), PlayPosition.GetInputRate(), PlayPosition.GetOutputRate());
return FQualifiedFrameTime(RootTime, PlayPosition.GetOutputRate());
}
FQualifiedFrameTime FSequencer::GetUnwarpedLocalTime() const
{
using namespace UE::MovieScene;
const FFrameRate FocusedResolution = GetFocusedTickResolution();
FQualifiedFrameTime CurrentPosition = GetGlobalTime();
CurrentPosition.Time = RootToUnwarpedLocalTransform.TransformTime(CurrentPosition.Time, FTransformTimeParams().IgnoreClamps());
return CurrentPosition;
}
FFrameTime FSequencer::GetScrubPosition() const
{
if (Settings->GetTimeWarpDisplayMode() == ESequencerTimeWarpDisplay::WarpedTime)
{
return GetLocalTime().Time;
}
return GetUnwarpedLocalTime().Time;
}
FFrameTime FSequencer::GetLastEvaluatedLocalTime() const
{
return LastEvaluatedLocalTime;
}
UE::Sequencer::FTimeDomainOverride FSequencer::OverrideTimeDomain(UE::Sequencer::ETimeDomain NewDomain)
{
return UE::Sequencer::FTimeDomainOverride(&TimeOperationDomain, NewDomain);
}
void FSequencer::SetLocalTime( FFrameTime NewTime, ESnapTimeMode SnapTimeMode, bool bEvaluate)
{
FFrameRate LocalResolution = GetFocusedTickResolution();
// Ensure the time is in the current view
if (IsAutoScrollEnabled())
{
ScrollIntoView(NewTime / LocalResolution);
}
// Perform snapping
if ((SnapTimeMode & ESnapTimeMode::STM_Interval) && Settings->GetForceWholeFrames())
{
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)
{
using namespace UE::MovieScene;
using namespace UE::Sequencer;
const FMovieSceneTransformBreadcrumbs& Breadcrumbs = GetPlaybackStatus() == EMovieScenePlayerStatus::Scrubbing
? ScrubStartBreadcrumbs
: CurrentTimeBreadcrumbs;
FMovieSceneInverseSequenceTransform LocalToRootTransform = TimeOperationDomain == ETimeDomain::Warped
? RootToWarpedLocalTransform.Inverse()
: RootToUnwarpedLocalTransform.Inverse();
// Transform the time to the root time-space
TOptional<FFrameTime> NewGlobalTime = LocalToRootTransform.TryTransformTime(NewTime + ScrubLinearOffset, Breadcrumbs,
EInverseEvaluateFlags::AnyDirection | EInverseEvaluateFlags::Cycle | EInverseEvaluateFlags::IgnoreClamps);
// If we still didn't find a time there's nothing we can do
if (NewGlobalTime.IsSet())
{
FTimeDomainOverride DomainOverride = OverrideTimeDomain(ETimeDomain::Unwarped);
SetGlobalTime(NewGlobalTime.GetValue(), bEvaluate);
}
}
void FSequencer::SetGlobalTime(FFrameTime NewTime, bool bEvaluate)
{
using namespace UE::MovieScene;
using namespace UE::Sequencer;
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()
if (PlayPosition.GetCurrentPosition() != NewTime)
{
// Make sure breadcrumbs are up to date
RootToWarpedLocalTransform.TransformTime(PlayPosition.GetCurrentPosition(), FTransformTimeParams().HarvestBreadcrumbs(CurrentTimeBreadcrumbs).IgnoreClamps());
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;
}
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::SnapSequencerTime(FFrameTime& ScrubTime)
{
// Clamp first, snap to frame last
if (GetSequencerSettings()->ShouldKeepCursorInPlayRangeWhileScrubbing())
{
TRange<FFrameNumber> PlaybackRange = GetSubSequenceRange().Get(GetRootMovieSceneSequence()->GetMovieScene()->GetPlaybackRange());
ScrubTime = UE::MovieScene::ClampToDiscreteRange(ScrubTime, PlaybackRange);
}
if (GetSequencerSettings()->GetForceWholeFrames())
{
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameRate DisplayRate = GetFocusedDisplayRate();
// Set the style of the scrub handle
if (GetScrubStyle() == ESequencerScrubberStyle::FrameBlock)
{
// Floor to the display frame
ScrubTime = ConvertFrameTime(ConvertFrameTime(ScrubTime, TickResolution, DisplayRate).FloorToFrame(), DisplayRate, TickResolution);
}
else
{
// Snap (round) to display rate
ScrubTime = FFrameRate::Snap(ScrubTime, TickResolution, DisplayRate);
}
}
if (GetSequencerSettings()->GetIsSnapEnabled())
{
using namespace UE::Sequencer;
TViewModelPtr<IClockExtension> Clock = GetViewModel()->GetRootSequenceModel().ImplicitCast();
if (Clock.IsValid() && Clock->SupportsSnapping() && Clock->ShouldSnapFrameTime())
{
ScrubTime = Clock->SnapFrameTime(ScrubTime);
}
}
}
void FSequencer::ForceEvaluate()
{
EvaluateInternal(PlayPosition.GetCurrentPositionAsRange());
}
void FSequencer::EvaluateInternal(FMovieSceneEvaluationRange InRange, bool bHasJumped)
{
using namespace UE::MovieScene;
// Ensure breadcrumbs are up to date
RootToUnwarpedLocalTransform.TransformTime(InRange.GetTime(), FTransformTimeParams().HarvestBreadcrumbs(CurrentTimeBreadcrumbs).IgnoreClamps());
if (!GlobalPlaybackWarpTransform.IsIdentity())
{
TRange<FFrameTime> NewRange = GlobalPlaybackWarpTransform.ComputeTraversedHull(InRange.GetRange());
InRange = FMovieSceneEvaluationRange(NewRange, InRange.GetFrameRate(), InRange.GetDirection());
}
LastEvaluatedLocalTime = GetLocalTime().Time;
if (Settings->ShouldCompileDirectorOnEvaluate())
{
RecompileDirtyDirectors();
}
bNeedsEvaluate = false;
UpdateCachedPlaybackContextAndClient();
if (EventContextsAttribute.IsBound())
{
CachedEventContexts.Reset();
for (UObject* Object : EventContextsAttribute.Get())
{
CachedEventContexts.Add(Object);
}
}
if (IMovieScenePlaybackClient* PlaybackClient = GetPlaybackClient())
{
PlaybackClient->WarpEvaluationRange(InRange);
}
FMovieSceneContext Context = FMovieSceneContext(InRange, PlaybackState).SetIsSilent(SilentModeCount != 0);
Context.SetHasJumped(bHasJumped);
RootTemplateInstance.EvaluateSynchronousBlocking(Context);
SuppressAutoEvalSignature.Reset();
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();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
State.ClearObjectCaches(*this);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
RestorePreAnimatedState();
CachedPlaybackContext = NewPlaybackContext;
CachedPlaybackClient = NewPlaybackClient;
OnPlaybackContextChanged();
}
}
void FSequencer::UpdateCachedCameraActors()
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const uint32 CurrentStateSerial = State.GetSerialNumber();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
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());
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const FMovieSceneObjectCache& ObjectCache = State.GetObjectCache(SequenceID);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
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)
{
if (!IsAutoScrollEnabled())
{
return;
}
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, CurrentTimeBreadcrumbs);
}
else if (AutoscrollOffset.GetValue() > 0 && LocalTime.AsSeconds() < ViewRange.GetUpperBoundValue() - Threshold)
{
SetLocalTimeLooped( (ViewRange.GetUpperBoundValue() - Threshold) * LocalTime.Rate, CurrentTimeBreadcrumbs);
}
}
// 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;
}
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
{
using namespace UE::MovieScene;
ISequencerModule& SequencerModule = FModuleManager::LoadModuleChecked<ISequencerModule>("Sequencer");
if (IMovieRendererInterface* MovieRenderer = SequencerModule.GetMovieRenderer(GetMovieRendererName()))
{
TArray<UMovieSceneCinematicShotSection*> ShotSections;
if (UMovieSceneCinematicShotSection* ShotSection = Cast<UMovieSceneCinematicShotSection>(FindSubSection(GetFocusedTemplateID())))
{
ShotSections.Add(ShotSection);
}
MovieRenderer->RenderMovie(GetRootMovieSceneSequence(), ShotSections);
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()))
{
auto Visit = [&Range](TRange<FFrameTime> RootRange)
{
Range = ConvertToDiscreteRange(RootRange);
return false;
};
SubSequenceData->RootToSequenceTransform.Inverse().TransformFiniteRangeWithinRange(ConvertToFrameTimeRange(Range), Visit, CurrentTimeBreadcrumbs, CurrentTimeBreadcrumbs);
}
}
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()
{
++SilentModeCount;
}
void FSequencer::ExitSilentMode()
{
--SilentModeCount;
ensure(SilentModeCount >= 0);
}
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;
}
void FSequencer::AddNumericTypeInterface(TSharedRef<UE::Sequencer::FSequencerNumericTypeInterface> InNumericTypeInterface)
{
NumericTypeInterfaces.AddUnique(InNumericTypeInterface);
}
void FSequencer::RemoveNumericTypeInterface(TSharedRef<UE::Sequencer::FSequencerNumericTypeInterface> InNumericTypeInterface)
{
NumericTypeInterfaces.Remove(InNumericTypeInterface);
}
TArrayView<const TSharedRef<UE::Sequencer::FSequencerNumericTypeInterface>> FSequencer::GetNumericTypeInterfaces() const
{
return NumericTypeInterfaces;
}
TSharedPtr<INumericTypeInterface<double>> FSequencer::GetNumericTypeInterface(UE::Sequencer::ENumericIntent Intent) const
{
TSharedPtr<INumericTypeInterface<double>> Result;
int32 Score = MIN_int32;
for (TSharedRef<UE::Sequencer::FSequencerNumericTypeInterface> Interface : NumericTypeInterfaces)
{
if (Interface->Intent != Intent)
{
continue;
}
const int32 ThisScore = Interface->GetRelevancyScore(*this, nullptr);
if (ThisScore > Score)
{
Result = Interface->Interface;
Score = ThisScore;
}
}
check(Result);
return Result.ToSharedRef();
}
TSharedRef<IPropertyTypeCustomization> FSequencer::MakeFrameNumberDetailsCustomization()
{
return MakeShared<FFrameNumberDetailsCustomization>(TWeakPtr<ISequencer>(AsShared()));
}
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 FSequencer::FindUnspawnedObjectGuid(UObject& InObject)
{
if (UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence())
{
UMovieScene* MovieScene = FocusedMovieSceneSequence->GetMovieScene();
// If the object is an archetype, the it relates to an unspawned spawnable.
UObject* ParentObject = FocusedMovieSceneSequence->GetParentObject(&InObject);
if (ParentObject && FMovieSceneSpawnable::IsSpawnableTemplate(*ParentObject))
{
FMovieSceneSpawnable* ParentSpawnable = MovieScene->FindSpawnable([&](FMovieSceneSpawnable& InSpawnable){
return InSpawnable.GetObjectTemplate() == ParentObject;
});
if (ParentSpawnable)
{
// TODO: Won't this always fail to find anything if the spawnable actor is unspawned?
// 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())
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
TArrayView<TWeakObjectPtr<>> BoundObjects = State.FindBoundObjects(ChildGuid, GetFocusedTemplateID(), GetSharedPlaybackState());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
const bool bHasObject = BoundObjects.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();
}
// Also check for custom spawnables
if (const FMovieSceneBindingReferences* BindingReferences = FocusedMovieSceneSequence->GetBindingReferences())
{
for (const FMovieSceneBindingReference& BindingReference : BindingReferences->GetAllReferences())
{
if (BindingReference.CustomBinding)
{
if (UMovieSceneSpawnableBindingBase* SpawnableBinding = BindingReference.CustomBinding->AsSpawnable(GetSharedPlaybackState()))
{
if (SpawnableBinding->SupportsObjectTemplates() && SpawnableBinding->GetObjectTemplate() == &InObject)
{
return BindingReference.ID;
}
}
}
}
}
}
}
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 ? FocusedMovieSceneSequence->GetMovieScene() : nullptr;
if (!FocusedMovieScene)
{
return FGuid();
}
if (FocusedMovieScene->IsReadOnly())
{
return FGuid();
}
// Attempt to resolve the object through the movie scene instance first,
FGuid ObjectGuid = bCreateHandleIfMissing ?
FindObjectId(*Object, ActiveTemplateIDs.Top()) :
FindCachedObjectId(*Object, ActiveTemplateIDs.Top());
if (ObjectGuid.IsValid())
{
// Check here for spawnable and custom spawnables otherwise spawnables get recreated as possessables, which doesn't make sense
if (MovieSceneHelpers::IsBoundToAnySpawnable(FocusedMovieSceneSequence, ObjectGuid, GetSharedPlaybackState()))
{
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);
}
if (ObjectGuid.IsValid() || IsReadOnly())
{
return ObjectGuid;
}
if (bCreateHandleIfMissing)
{
UObject* PlaybackContext = PlaybackContextAttribute.Get(nullptr);
if (FocusedMovieSceneSequence->CanPossessObject(*Object, PlaybackContext))
{
ObjectGuid = FSequencerUtilities::CreateBinding(AsShared(), *Object);
if (CreatedFolderName != NAME_None)
{
// Find the outermost object and put it in a
FMovieScenePossessable* Possessable = FocusedMovieScene->FindPossessable(ObjectGuid);
if (!Possessable || !Possessable->GetParent().IsValid())
{
UMovieSceneFolder* Folder = FSequencer::CreateFoldersRecursively(TArray<FName>{ CreatedFolderName }, 0, FocusedMovieScene, nullptr, FocusedMovieScene->GetRootFolders());
if (Folder)
{
Folder->AddChildObjectBinding(ObjectGuid);
}
}
}
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemAdded );
}
}
return ObjectGuid;
}
ISequencerObjectChangeListener& FSequencer::GetObjectChangeListener()
{
return *ObjectChangeListener;
}
ISequencerPropertyKeyedStatusHandler& FSequencer::GetPropertyKeyedStatusHandler()
{
return *PropertyKeyedStatusHandler;
}
TSharedPtr<class ITimeSlider> FSequencer::GetTopTimeSliderWidget() const
{
return SequencerWidget->GetTopTimeSliderWidget();
}
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;
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);
}
}
}
UMovieSceneEntitySystemLinker* FSequencer::ConstructEntitySystemLinker()
{
UObject* PlaybackContext = GetPlaybackContext();
UWorld* World = PlaybackContext ? PlaybackContext->GetWorld() : nullptr;
UObject* Outer = GetTransientPackage();
FName EntitySystemLinkerName = MakeUniqueObjectName(Outer, UMovieSceneEntitySystemLinker::StaticClass(), TEXT("SequencerEntitySystemLinker"));
UMovieSceneEntitySystemLinker* EntitySystemLinker = NewObject<UMovieSceneEntitySystemLinker>(Outer, EntitySystemLinkerName);
EntitySystemLinker->SetWorld(World);
EntitySystemLinker->SetLinkerRole(UE::MovieScene::EEntitySystemLinkerRole::Standalone);
return EntitySystemLinker;
}
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)
{
// We may have been waiting for our time controller to tell us it was ready to play,
// so TimeControllerState may be ETimeControllerState::PreparingToPlay. Here we reset that back to its
// initialized state of true.
TimeControllerState = ETimeControllerState::ReadyToPlay;
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 (TimeUndoRedoHandler.UndoRedoProxy)
{
Collector.AddReferencedObject(TimeUndoRedoHandler.UndoRedoProxy);
}
if (RootSequence.Get())
{
Collector.AddReferencedObject( RootSequence );
}
Collector.AddPropertyReferences(FMovieSceneRootEvaluationTemplateInstance::StaticStruct(), &RootTemplateInstance, nullptr);
}
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
ViewModel->GetSelection()->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();
for (TSharedRef<UE::Sequencer::FSequencerNumericTypeInterface> Interface : GetNumericTypeInterfaces())
{
if (Interface->Interface->GetOnSettingChanged() != nullptr)
{
Interface->Interface->GetOnSettingChanged()->Broadcast();
}
}
// @todo run through all tracks for new movie scene changes
// needed for audio track decompression
}
void FSequencer::RefreshUI()
{
using namespace UE::Sequencer;
ViewModel->GetSelection()->Empty();
UMovieSceneSequence* FocusedSequence = GetFocusedMovieSceneSequence();
FMovieSceneSequenceID SequenceID = GetFocusedTemplateID();
TSharedPtr<FSequenceModel> RootSequenceModel = ViewModel->GetRootModel().ImplicitCast();
RootSequenceModel->SetSequence(nullptr, MovieSceneSequenceID::Root);
RootSequenceModel->SetSequence(FocusedSequence, SequenceID);
RefreshTree();
}
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::MakePlayTimeDisplay()
{
return SNew(STemporarilyFocusedSpinBox<double>)
.Style(&FAppStyle::GetWidgetStyle<FSpinBoxStyle>("Sequencer.PlayTimeSpinBox"))
.PreventThrottling(true)
.Value_Lambda([this]() -> double {
return GetLocalTime().Time.GetFrame().Value;
})
.OnValueChanged_Lambda([this](double InFrame) {
double CurrentTime = GetLocalTime().Time.AsDecimal();
if (CurrentTime != InFrame)
{
SequencerWidget->SetPlayTimeClampedByWorkingRange(InFrame);
}
})
.OnValueCommitted_Lambda([this](double InFrame, ETextCommit::Type) {
double CurrentTime = GetLocalTime().Time.AsDecimal();
if (CurrentTime != InFrame)
{
SequencerWidget->SetPlayTime(InFrame);
}
})
.MinValue(TOptional<double>())
.MaxValue(TOptional<double>())
.ToolTipText_Lambda([this] {
FFrameRate TickResolution = GetFocusedTickResolution();
FFrameRate DisplayRate = GetFocusedDisplayRate();
FFrameNumber CurrentFrame = GetLocalTime().Time.GetFrame();
TRange<FFrameNumber> CurrentRange = GetSubSequenceRange().IsSet() ? GetSubSequenceRange().GetValue() : GetPlaybackRange();
FFrameNumber FrameCount = FFrameRate::TransformTime((CurrentFrame - CurrentRange.GetLowerBoundValue() + 1).Value, TickResolution, DisplayRate).CeilToFrame();
FFrameNumber FrameDuration = FFrameRate::TransformTime(CurrentRange.Size<FFrameNumber>().Value, TickResolution, DisplayRate).CeilToFrame();
return FText::Format(LOCTEXT("FrameCountTooltip", "{0} of {1}"), FrameCount.Value, FrameDuration.Value);
})
.TypeInterface(this, &FSequencer::GetNumericTypeInterface, UE::Sequencer::ENumericIntent::Position)
.Delta(this, &FSequencer::GetDisplayRateDeltaFrameCount)
.LinearDeltaSensitivity(25)
.MinDesiredWidth_Lambda([this] {
TRange<double> ViewRange = GetViewRange();
FString LowerBoundStr = GetNumericTypeInterface()->ToString(ViewRange.GetLowerBoundValue());
FString UpperBoundStr = GetNumericTypeInterface()->ToString(ViewRange.GetUpperBoundValue());
// Always measure with the negative and subframe indicator so that the size doesn't change when there is and isn't a subframe
if (!LowerBoundStr.Contains(TEXT("*")))
{
LowerBoundStr += TEXT("*");
}
if (!LowerBoundStr.Contains(TEXT("-")))
{
LowerBoundStr += TEXT("-");
}
if (!UpperBoundStr.Contains(TEXT("*")))
{
UpperBoundStr += TEXT("*");
}
if (!UpperBoundStr.Contains(TEXT("-")))
{
UpperBoundStr += TEXT("-");
}
const FSlateFontInfo NormalFont = FCoreStyle::Get().GetFontStyle(TEXT("NormalFont"));
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
FVector2D LowerTextSize = FontMeasureService->Measure(LowerBoundStr, NormalFont);
FVector2D UpperTextSize = FontMeasureService->Measure(UpperBoundStr, NormalFont);
return FMath::Max(LowerTextSize.X, UpperTextSize.X);
});
}
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(), "Animation.PlayControlsButton")
.ContentPadding(0.0f)
.IsFocusable(false)
[
SNew(SImage)
.ColorAndOpacity_Lambda([this]() { return Settings->GetPlaybackRangeStartColor(); })
.Image(FAppStyle::Get().GetBrush("Sequencer.Transport.SetPlayStart"))
];
}
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(), "Animation.PlayControlsButton")
.ContentPadding(0.0f)
.IsFocusable(false)
[
SNew(SImage)
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
.Image(FAppStyle::Get().GetBrush("Sequencer.Transport.JumpToPreviousKey"))
];
}
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(), "Animation.PlayControlsButton")
.ContentPadding(0.0f)
.IsFocusable(false)
[
SNew(SImage)
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
.Image(FAppStyle::Get().GetBrush("Sequencer.Transport.JumpToNextKey"))
];
}
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(), "Animation.PlayControlsButton")
.ContentPadding(0.0f)
.IsFocusable(false)
[
SNew(SImage)
.ColorAndOpacity_Lambda([this]() { return Settings->GetPlaybackRangeEndColor(); })
.Image(FAppStyle::Get().GetBrush("Sequencer.Transport.SetPlayEnd"))
];
}
TSharedRef<SWidget> FSequencer::OnCreateTransportLoopMode()
{
TSharedRef<SButton> LoopButton = SNew(SButton)
.OnClicked(this, &FSequencer::OnCycleLoopMode)
.ButtonStyle( FAppStyle::Get(), "Animation.PlayControlsButton" )
.IsFocusable(false)
.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(0.0f);
TWeakPtr<SButton> WeakButton = LoopButton;
LoopButton->SetContent(SNew(SImage)
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
.Image_Lambda([&, WeakButton]()
{
if (GetLoopMode() == ESequencerLoopMode::SLM_NoLoop)
{
return FAppStyle::Get().GetBrush("Animation.Loop.Disabled");
}
else if (GetLoopMode() == ESequencerLoopMode::SLM_Loop)
{
return FAppStyle::Get().GetBrush("Animation.Loop.Enabled");
}
else
{
return FAppStyle::Get().GetBrush("Animation.Loop.SelectionRange");
}
})
);
return LoopButton;
}
TSharedRef<SWidget> FSequencer::OnCreateTransportRecord()
{
TSharedRef<SButton> RecordButton = SNew(SButton)
.OnClicked(this, &FSequencer::OnRecord)
.ButtonStyle( FAppStyle::Get(), "Animation.PlayControlsButton" )
.IsFocusable(false)
.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(0.0f);
TWeakPtr<SButton> WeakButton = RecordButton;
RecordButton->SetContent(SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Animation.Record"))
.ColorAndOpacity_Lambda([this]()
{
if (OnGetIsRecording().IsBound() && OnGetIsRecording().Execute())
{
if (!RecordingAnimation.IsPlaying())
{
RecordingAnimation.Play(SequencerWidget.ToSharedRef(), true);
}
FLinearColor Color = FLinearColor::White;
return FSlateColor(Color.CopyWithNewOpacity(0.2f + 0.8f * RecordingAnimation.GetLerp()));
}
RecordingAnimation.Pause();
return FSlateColor::UseSubduedForeground();
})
);
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
UObject* ParentObject = MovieSceneHelpers::GetObjectTemplate(Sequence, Possessable->GetParent(), GetSharedPlaybackState());
if (ParentObject)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
TArrayView<TWeakObjectPtr<>> BoundObjects = State.FindBoundObjects(BindingId, GetFocusedTemplateID(), GetSharedPlaybackState());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
for (TWeakObjectPtr<> WeakObj : BoundObjects)
{
if (UObject* Obj = WeakObj.Get())
{
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 (UObject* Template = MovieSceneHelpers::GetObjectTemplate(Sequence, BindingId, GetSharedPlaybackState()))
{
return Template;
}
return nullptr;
}
void
FSequencer::FCachedViewState::StoreViewState()
{
if (bValid)
{
return;
}
bValid = true;
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;
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);
//if turn off game view now need to make sure widget/gizmo is on
if (GameViewStates[Index].Value == false)
{
LevelVC->ShowWidget(true);
}
}
}
}
GameViewStates.Empty();
}
FReply FSequencer::OnPlay(bool bTogglePlay)
{
if( PlaybackState == EMovieScenePlayerStatus::Playing && bTogglePlay )
{
Pause();
}
else
{
TRange<FFrameNumber> RootTimeBounds = GetRootTimeBounds();
FFrameNumber MinInclusiveTime = UE::MovieScene::DiscreteInclusiveLower(RootTimeBounds);
FFrameNumber MaxInclusiveTime = UE::MovieScene::DiscreteExclusiveUpper(RootTimeBounds) - 1;
FFrameTime GlobalTime = GlobalPlaybackWarpTransform.TransformTime(GetGlobalTime().Time);
if (GlobalTime <= MinInclusiveTime || GlobalTime >= MaxInclusiveTime)
{
SetGlobalTime(PlaybackSpeed > 0 ? MinInclusiveTime : MaxInclusiveTime);
}
SpeedIndexBeforePlay = CurrentSpeedIndex;
PlaybackSpeedBeforePlay = PlaybackSpeed;
TimeController->PrepareToPlay(GetGlobalTime());
if (TimeController->IsReadyToPlay())
{
SetPlaybackStatus(EMovieScenePlayerStatus::Playing);
TimeControllerState = ETimeControllerState::ReadyToPlay;
}
else
{
TimeControllerState = ETimeControllerState::PreparingToPlay;
}
// 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)
{
using namespace UE::Sequencer;
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
FFrameRate DisplayRate = GetFocusedDisplayRate();
FQualifiedFrameTime CurrentTime = GetUnwarpedLocalTime();
FFrameTime NewPosition = FFrameRate::TransformTime((CurrentTime.ConvertTo(DisplayRate) + Increment).FloorToFrame(), DisplayRate, CurrentTime.Rate);
FTimeDomainOverride TimeDomain = OverrideTimeDomain(ETimeDomain::Unwarped);
SetLocalTime(NewPosition, ESnapTimeMode::STM_Interval);
return FReply::Handled();
}
FReply FSequencer::OnStepBackward(FFrameNumber Increment)
{
using namespace UE::Sequencer;
SetPlaybackStatus(EMovieScenePlayerStatus::Stepping);
FFrameRate DisplayRate = GetFocusedDisplayRate();
FQualifiedFrameTime CurrentTime = GetUnwarpedLocalTime();
FFrameTime NewPosition = FFrameRate::TransformTime((CurrentTime.ConvertTo(DisplayRate) - Increment).FloorToFrame(), DisplayRate, CurrentTime.Rate);
FTimeDomainOverride TimeDomain = OverrideTimeDomain(ETimeDomain::Unwarped);
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->GetForceWholeFrames();
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 (ViewModel->GetSelection()->Outliner.Num())
{
GetKeysFromSelection(SelectedKeyCollection, SMALL_NUMBER);
}
else
{
GetAllKeys(SelectedKeyCollection, SMALL_NUMBER);
}
if (SelectedKeyCollection.IsValid())
{
TRange<FFrameNumber> Range = GetTimeBounds();
FFrameNumber FrameNumber = GetLocalTime().Time.FloorToFrame();
TOptional<FFrameNumber> NewTime = SelectedKeyCollection->GetNextKey(FrameNumber, EFindKeyDirection::Backwards, Range, EFindKeyType::FKT_All);
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 (ViewModel->GetSelection()->Outliner.Num())
{
GetKeysFromSelection(SelectedKeyCollection, SMALL_NUMBER);
}
else
{
GetAllKeys(SelectedKeyCollection, SMALL_NUMBER);
}
if (SelectedKeyCollection.IsValid())
{
TRange<FFrameNumber> Range = GetTimeBounds();
FFrameNumber FrameNumber = GetLocalTime().Time.FloorToFrame();
TOptional<FFrameNumber> NewTime = SelectedKeyCollection->GetNextKey(FrameNumber, EFindKeyDirection::Forwards, Range, EFindKeyType::FKT_All);
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, const FMovieSceneTransformBreadcrumbs& Breadcrumbs)
{
using namespace UE::MovieScene;
using namespace UE::Sequencer;
TOptional<EMovieScenePlayerStatus::Type> NewPlaybackStatus;
FMovieSceneInverseSequenceTransform LocalToRootTransform = TimeOperationDomain == ETimeDomain::Unwarped
? RootToUnwarpedLocalTransform.Inverse()
: RootToWarpedLocalTransform.Inverse();
// Default to the CurrentTimeBreadcrumbs
const FMovieSceneTransformBreadcrumbs& BreadcrumbsToUse = Breadcrumbs.Num() == 0 ? CurrentTimeBreadcrumbs : Breadcrumbs;
TOptional<FFrameTime> NewGlobalTime;
bool bResetPosition = false;
bool bHasJumped = false;
bool bRestarted = false;
if (PauseOnFrame.IsSet() && ((PlaybackSpeed > 0 && NewLocalTime > PauseOnFrame.GetValue()) || (PlaybackSpeed < 0 && NewLocalTime < PauseOnFrame.GetValue())))
{
NewGlobalTime = LocalToRootTransform.TryTransformTime(PauseOnFrame.GetValue(), BreadcrumbsToUse);
if (NewGlobalTime)
{
PauseOnFrame.Reset();
bResetPosition = true;
NewPlaybackStatus = EMovieScenePlayerStatus::Stopped;
}
}
else if (GetLoopMode() == ESequencerLoopMode::SLM_Loop || GetLoopMode() == ESequencerLoopMode::SLM_LoopSelectionRange)
{
TRange<FFrameNumber> TimeBounds = GetTimeBounds();
FFrameNumber MinInclusiveTime = UE::MovieScene::DiscreteInclusiveLower(TimeBounds);
FFrameNumber MaxInclusiveTime = UE::MovieScene::DiscreteExclusiveUpper(TimeBounds) - 1;
FFrameTime LoopTime = NewLocalTime;
if (TimeOperationDomain == ETimeDomain::Unwarped && LocalToWarpedLocalTransform.FindFirstWarpDomain() == ETimeWarpChannelDomain::PlayRate)
{
LoopTime = LocalToWarpedLocalTransform.TransformTime(LoopTime);
}
if (LoopTime < MinInclusiveTime || LoopTime > MaxInclusiveTime)
{
NewGlobalTime = LocalToRootTransform.TryTransformTime((PlaybackSpeed > 0 ? MinInclusiveTime : MaxInclusiveTime), BreadcrumbsToUse);
if (NewGlobalTime)
{
bResetPosition = true;
bHasJumped = true;
}
}
else
{
NewGlobalTime = LocalToRootTransform.TryTransformTime(NewLocalTime, BreadcrumbsToUse);
}
}
else
{
NewGlobalTime = LocalToRootTransform.TryTransformTime(NewLocalTime, BreadcrumbsToUse);
if (NewGlobalTime)
{
FFrameTime NewWarpedGlobalTime = GlobalPlaybackWarpTransform.TransformTime(NewGlobalTime.GetValue());
FFrameTime OldWarpedGlobalTime = GlobalPlaybackWarpTransform.TransformTime(GetGlobalTime().Time);
TRange<FFrameNumber> RootTimeBounds = GetRootTimeBounds();
FFrameNumber MinInclusiveTime = UE::MovieScene::DiscreteInclusiveLower(RootTimeBounds);
FFrameNumber MaxInclusiveTime = UE::MovieScene::DiscreteExclusiveUpper(RootTimeBounds) - 1;
bool bReachedEnd = false;
if (PlaybackSpeed > 0)
{
bReachedEnd = OldWarpedGlobalTime <= MaxInclusiveTime && NewWarpedGlobalTime >= MaxInclusiveTime;
}
else
{
bReachedEnd = OldWarpedGlobalTime >= MinInclusiveTime && NewWarpedGlobalTime <= MinInclusiveTime;
}
// Stop if we hit the playback range end
if (bReachedEnd)
{
NewGlobalTime = GlobalPlaybackWarpTransform.Inverse().TryTransformTime(PlaybackSpeed > 0 ? MaxInclusiveTime : MinInclusiveTime);
NewPlaybackStatus = EMovieScenePlayerStatus::Stopped;
}
}
}
if (!NewGlobalTime)
{
NewGlobalTime = LocalToRootTransform.TryTransformTime(NewLocalTime, BreadcrumbsToUse);
}
if (!NewGlobalTime && RootToUnwarpedLocalTransform.NestedTransforms.Num() > 0)
{
// If we couldn't transform the time, we try one last-ditch attempt to guess a cycling or looping
// transformation based on the linear transformation between each of the breadcrumbs.
FMovieSceneTransformBreadcrumbs DenseBreadcrumbs;
FFrameTime CurrentLocalTime = RootToUnwarpedLocalTransform.TransformTime(GetGlobalTime().Time, FTransformTimeParams().HarvestBreadcrumbs(DenseBreadcrumbs));
FFrameTime LocalDeltaGuess = NewLocalTime - CurrentLocalTime;
FFrameTime NewLocalTimeGuess = RootToUnwarpedLocalTransform.NestedTransforms.Last().TransformTime(LocalDeltaGuess);
NewGlobalTime = LocalToRootTransform.TryTransformTime(NewLocalTimeGuess, DenseBreadcrumbs);
}
if (!NewGlobalTime)
{
return;
}
FFrameRate RootTickResolution = GetRootTickResolution();
// Ensure the time is in the current view - must occur before the time cursor changes
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
if (IsAutoScrollEnabled())
{
ScrollIntoView((NewGlobalTime.GetValue() * RootToUnwarpedLocalTransform) / RootTickResolution);
}
FFrameTime NewPlayPosition = ConvertFrameTime(NewGlobalTime.GetValue(), 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.GetValue(), RootTickResolution));
}
// Ensure breadcrumbs are up to date
RootToUnwarpedLocalTransform.TransformTime(NewGlobalTime.GetValue(), FTransformTimeParams().HarvestBreadcrumbs(CurrentTimeBreadcrumbs).IgnoreClamps());
// 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(PlayPosition.GetCurrentPositionAsRange());
if (NewPlaybackStatus.GetValue() == EMovieScenePlayerStatus::Stopped)
{
RestorePlaybackSpeedAfterPlay();
OnStopDelegate.Broadcast();
}
}
}
void FSequencer::SetPlaybackSpeed(float InPlaybackSpeed)
{
PlaybackSpeed = InPlaybackSpeed;
const bool bExactOnly = true;
CurrentSpeedIndex = FindClosestPlaybackSpeed(InPlaybackSpeed, bExactOnly);
}
void FSequencer::RestorePlaybackSpeedAfterPlay()
{
// Reset the speed to what it was before we started playing, in case the user increased/decreased the speed while it
// was playing.
CurrentSpeedIndex = SpeedIndexBeforePlay;
PlaybackSpeed = PlaybackSpeedBeforePlay;
}
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;
}
// We have to update subsequence data before getting the local time on a new subsequence
UpdateSubSequenceData();
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 (ActiveTemplateIDs.Num() == 1)
{
return GetRootTimeBounds();
}
if (Settings->ShouldEvaluateSubSequencesInIsolation())
{
return FocusedSequence->GetMovieScene()->GetPlaybackRange();
}
return SubSequenceRange;
}
TRange<FFrameNumber> FSequencer::GetRootTimeBounds() const
{
const UMovieSceneSequence* RootMovieSceneSequence = GetRootMovieSceneSequence();
if (!RootMovieSceneSequence)
{
return TRange<FFrameNumber>(-100000, 100000);
}
return RootMovieSceneSequence->GetMovieScene()->GetPlaybackRange();
}
void FSequencer::RefreshSupportedCustomBindingTypes()
{
MovieSceneHelpers::GetPrioritySortedCustomBindingTypes(SupportedCustomBindingTypes);
}
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();
const bool bComputeNearest = EnumHasAnyFlags(NearestKeyOption, ENearestKeyOption::NKO_SearchKeys | ENearestKeyOption::NKO_SearchSections | ENearestKeyOption::NKO_SearchMarkers);
if (!bComputeNearest)
{
return CurrentTime;
}
if (ViewModel->GetSelection()->Outliner.Num() == 0)
{
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, EFindKeyType::FKT_Keys);
TRange<FFrameNumber> FindRangeForwards(CurrentTime, TRangeBound<FFrameNumber>::Open());
TOptional<FFrameNumber> NewTimeForwards = SelectedKeyCollection->FindFirstKeyInRange(FindRangeForwards, EFindKeyDirection::Forwards, EFindKeyType::FKT_Keys);
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->FindFirstKeyInRange(FindRangeBackwards, EFindKeyDirection::Backwards, EFindKeyType::FKT_Sections);
TRange<FFrameNumber> FindRangeForwards(CurrentTime, TRangeBound<FFrameNumber>::Open());
TOptional<FFrameNumber> NewTimeForwards = SelectedKeyCollection->FindFirstKeyInRange(FindRangeForwards, EFindKeyDirection::Forwards, EFindKeyType::FKT_Sections);
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)
{
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)
{
NewScrubPosition += ScrubLinearOffset;
OnEndScrubbing();
}
else
{
UpdateAutoScroll(NewScrubPosition / GetFocusedTickResolution());
// When scrubbing, we animate auto-scrolled scrub position in Tick()
if (AutoscrubOffset.IsSet())
{
return;
}
}
}
if (!bScrubbing && CVarAutoScrub->GetBool() && FSlateApplication::Get().GetModifierKeys().IsShiftDown())
{
AutoScrubToTime(NewScrubPosition);
}
else if (bEvaluate)
{
// Evaluation can be expensive and we may receive multiple OnScrubPositionChanged events in
// a frame, so defer the evaluation until the next tick
PendingScrubPosition = MakeTuple(NewScrubPosition, TimeOperationDomain);
}
else
{
SetLocalTimeDirectly(NewScrubPosition, bEvaluate);
}
}
void FSequencer::OnBeginScrubbing()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Scrubbing);
SequencerWidget->RegisterActiveTimerForPlayback();
ScrubLinearOffset = FFrameTime(0);
ScrubStartBreadcrumbs = CurrentTimeBreadcrumbs;
OnBeginScrubbingDelegate.Broadcast();
}
void FSequencer::OnEndScrubbing()
{
SetPlaybackStatus(EMovieScenePlayerStatus::Stopped);
AutoscrubOffset.Reset();
StopAutoscroll();
ScrubStartBreadcrumbs = FMovieSceneTransformBreadcrumbs();
ScrubLinearOffset = FFrameTime(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
{
using namespace UE::MovieScene;
const bool bWarpedTime = Settings->GetTimeWarpDisplayMode() == ESequencerTimeWarpDisplay::WarpedTime;
const FMovieSceneSequenceTransform* RootToParentChainTransform = bWarpedTime
? &RootToWarpedLocalTransform
: &RootToUnwarpedLocalTransform;
if (ScrubPositionParent.IsSet() && ScrubPositionParent.GetValue() != MovieSceneSequenceID::Root)
{
if (const FMovieSceneSequenceHierarchy* Hierarchy = CompiledDataManager->FindHierarchy(RootTemplateInstance.GetCompiledDataID()))
{
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : Hierarchy->AllSubSequenceData())
{
if (Pair.Key == ScrubPositionParent.GetValue())
{
RootToParentChainTransform = bWarpedTime
? &Pair.Value.RootToSequenceTransform
: &Pair.Value.RootToUnwarpedLocalTransform;
break;
}
}
}
}
FFrameTime LocalTime = RootToParentChainTransform->TransformTime(GetGlobalTime().Time, FTransformTimeParams().IgnoreClamps());
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);
}
}
void FSequencer::BrowseToObject()
{
using namespace UE::Sequencer;
TArray<UObject*> ObjectsToFocus;
for (TViewModelPtr<FSectionModel> SectionModel : ViewModel->GetSelection()->TrackArea.Filter<FSectionModel>())
{
if (UMovieSceneSection* Section = SectionModel->GetSection())
{
if (UObject* ObjectToFocus = Section->GetSourceObject())
{
ObjectsToFocus.Add(ObjectToFocus);
}
}
}
if (ObjectsToFocus.Num() > 0)
{
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() && GetFocusedMovieSceneSequence()->GetMovieScene()->IsReadOnly());
}
void FSequencer::ExternalSelectionHasChanged()
{
SynchronizeSequencerSelectionWithExternalSelection();
}
FGuid FSequencer::MakeNewSpawnable(UObject& Object, UActorFactory* ActorFactory, bool bSetupDefaults)
{
const FScopedTransaction Transaction(LOCTEXT("UndoAddingObject", "Add Object to MovieScene"));
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
FGuid NewGuid = FSequencerUtilities::MakeNewSpawnable(AsShared(), Object, ActorFactory, bSetupDefaults);
if (SelectedParentFolders.Num() > 0)
{
SelectedParentFolders[0]->AddChildObjectBinding(NewGuid);
}
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->AddTrack<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))
{
if (WeakObject.IsValid())
{
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();
const TViewModelPtr<IOutlinerExtension> OutlinerItem = NodeToBeDeleted->CastThisShared<IOutlinerExtension>();
if (FilterBar->HasIsolatedTracks())
{
FilterBar->UnisolateTracks({ OutlinerItem });
}
if (FilterBar->HasHiddenTracks())
{
FilterBar->UnhideTracks({ OutlinerItem });
}
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();
}
NodeTree->SortAllNodesAndDescendants();
NodeTree->RequestFilterUpdate();
OnActivateSequenceEvent.Broadcast(ActiveTemplateIDs.Top());
}
void FSequencer::OnNewActorsDropped(const TArray<UObject*>& DroppedObjects, const TArray<AActor*>& DroppedActors)
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* OwnerMovieScene = Sequence->GetMovieScene();
bool bAddSpawnable = FSlateApplication::Get().GetModifierKeys().IsShiftDown() && Sequence->AllowsSpawnableObjects();
bool bAddPossessable = FSlateApplication::Get().GetModifierKeys().IsControlDown();
bool bAddReplaceable = FSlateApplication::Get().GetModifierKeys().IsAltDown();
if (bAddSpawnable || bAddPossessable || bAddReplaceable)
{
TArray<AActor*> SpawnedActors;
const FScopedTransaction Transaction(LOCTEXT("UndoAddActors", "Add Actors to Sequencer"));
if (OwnerMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
Sequence->Modify();
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
UMovieSceneFolder* ParentFolder = SelectedParentFolders.Num() > 0 ? SelectedParentFolders[0] : nullptr;
for ( AActor* Actor : DroppedActors )
{
if (AActor* NewActor = Actor)
{
FGuid PossessableGuid = FSequencerUtilities::CreateBinding(AsShared(), *NewActor);
FGuid NewGuid = PossessableGuid;
OnActorAddedToSequencerEvent.Broadcast(NewActor, PossessableGuid);
if (bAddSpawnable || bAddReplaceable)
{
TSubclassOf<UMovieSceneCustomBinding> CustomBindingClass = bAddSpawnable ? UMovieSceneSpawnableActorBinding::StaticClass() : UMovieSceneReplaceableActorBinding::StaticClass();
const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences();
if (BindingReferences)
{
for (const FMovieSceneBindingReference& Reference : BindingReferences->GetReferences(PossessableGuid))
{
for (const TSubclassOf<UMovieSceneCustomBinding>& SupportedCustomBindingType : SupportedCustomBindingTypes)
{
if (SupportedCustomBindingType && SupportedCustomBindingType->IsChildOf(CustomBindingClass) &&
SupportedCustomBindingType->GetDefaultObject<UMovieSceneCustomBinding>()->SupportsConversionFromBinding(Reference, Actor))
{
FMovieScenePossessable* NewPossessable = FSequencerUtilities::ConvertToCustomBinding(AsShared(), NewGuid, CustomBindingClass);
if (NewPossessable)
{
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(NewPossessable->GetGuid(), ActiveTemplateIDs.Top()))
{
AActor* SpawnedActor = Cast<AActor>(WeakObject.Get());
if (SpawnedActor)
{
SpawnedActors.Add(SpawnedActor);
NewActor = SpawnedActor;
}
}
NewGuid = NewPossessable->GetGuid();
}
break;
}
}
}
}
}
if (NewActor && (NewActor->GetClass() == ACameraRig_Rail::StaticClass() || NewActor->GetClass() == ACameraRig_Crane::StaticClass()))
{
ACineCameraActor* OutActor;
FSequencerUtilities::CreateCameraWithRig(AsShared(), NewActor, bAddSpawnable, OutActor);
}
if (ParentFolder)
{
ParentFolder->AddChildObjectBinding(NewGuid);
}
}
}
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::SetShowCurveEditor(bool bInShowCurveEditor)
{
SequencerWidget->OnCurveEditorVisibilityChanged(bInShowCurveEditor);
GetSequencerSettings()->SetCurveEditorVisible(bInShowCurveEditor);
}
bool FSequencer::GetCurveEditorIsVisible() const
{
using namespace UE::Sequencer;
// 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)
FCurveEditorExtension* CurveEditorExtension = ViewModel->CastDynamic<FCurveEditorExtension>();
if (ensure(CurveEditorExtension))
{
return CurveEditorExtension->IsCurveEditorOpen();
}
return false;
}
void FSequencer::SaveCurrentMovieScene()
{
UE::Sequencer::CaptureThumbnailForAssetBlocking(
*GetCurrentAsset(),
*this,
Settings->GetThumbnailCaptureSettings()
);
OnPreSaveEvent.Broadcast(*this);
TArray<UPackage*> PackagesToSave;
TArray<UMovieScene*> MovieScenesToSave;
MovieSceneHelpers::GetDescendantMovieScenes(GetRootMovieSceneSequence(), MovieScenesToSave);
for (UMovieScene* MovieSceneToSave : MovieScenesToSave)
{
UPackage* MovieScenePackageToSave = MovieSceneToSave->GetOuter()->GetOutermost();
if (MovieScenePackageToSave->IsDirty())
{
PackagesToSave.AddUnique(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;
const FScopedTransaction Transaction(LOCTEXT("UndoPossessingObject", "Possess Object in Sequencer"));
TArray<FGuid> PossessableGuids = FSequencerUtilities::AddActors(AsShared(), InActors);
if (PossessableGuids.Num())
{
// Check if a folder is selected so we can add the actors to the selected folder.
TArray<UMovieSceneFolder*> SelectedParentFolders;
FString NewNodePath;
if (ViewModel->GetSelection()->Outliner.Num() > 0)
{
for (FViewModelPtr CurrentItem : ViewModel->GetSelection()->Outliner)
{
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;
}
FGuid FSequencer::AddEmptyBinding()
{
using namespace UE::Sequencer;
const FScopedTransaction Transaction(LOCTEXT("UndoAddEmptyBinding", "Add Empty Binding to Sequencer"));
FGuid PossessableGuid;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
if (!Sequence)
{
return PossessableGuid;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (!MovieScene)
{
return PossessableGuid;
}
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return PossessableGuid;
}
Sequence->Modify();
UObject* Context = GetPlaybackContext();
// Create a new binding for this object
TArray<FName> PossessableNames;
for (int32 i = 0; i < MovieScene->GetPossessableCount(); ++i)
{
PossessableNames.Add(*MovieScene->GetPossessable(i).GetName());
}
FName PossessableName = FSequencerUtilities::GetUniqueName(TEXT("EmptyBinding"), PossessableNames);
UE::Sequencer::FCreateBindingParams CreateBindingParams;
CreateBindingParams.bAllowCustomBinding = false;
CreateBindingParams.BindingNameOverride = PossessableName.ToString();
CreateBindingParams.bAllowEmptyBinding = true;
PossessableGuid = FSequencerUtilities::CreateOrReplaceBinding(SharedThis(this), nullptr, CreateBindingParams);
RefreshTree();
return PossessableGuid;
}
void FSequencer::OnSelectionChanged()
{
HandleSelectedOutlinerNodesChanged();
}
void FSequencer::HandleSelectedOutlinerNodesChanged()
{
using namespace UE::Sequencer;
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();
}
}
TSet<UMovieSceneTrack*> SelectedTracks;
for (TViewModelPtr<ITrackExtension> TrackModel : ViewModel->GetSelection()->Outliner.Filter<ITrackExtension>())
{
if (UMovieSceneTrack* Track = TrackModel->GetTrack())
{
SelectedTracks.Add(Track);
}
}
TArray<UMovieSceneSection*> SelectedSections;
for (TViewModelPtr<FSectionModel> SectionModel : ViewModel->GetSelection()->TrackArea.Filter<FSectionModel>())
{
if (UMovieSceneSection* Section = SectionModel->GetSection())
{
SelectedSections.Add(Section);
}
}
OnSelectionChangedObjectGuidsDelegate.Broadcast(ViewModel->GetSelection()->GetBoundObjectsGuids());
OnSelectionChangedTracksDelegate.Broadcast(SelectedTracks.Array());
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;
}
if (ViewModel->GetSelection()->Outliner.Num() == 0)
{
return;
}
TSet<FString> NodesToAdd;
for (FViewModelPtr Item : ViewModel->GetSelection()->Outliner)
{
TSharedPtr<IGroupableExtension> Groupable = Item->FindAncestorOfType<IGroupableExtension>(true);
if (Groupable)
{
NodesToAdd.Add(IOutlinerExtension::GetPathName(Item));
}
}
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(ViewModel->GetSelection()->Outliner.GetSelected().Array(), NodeGroup);
}
void FSequencer::AddNodesToExistingNodeGroup(TArrayView<const UE::Sequencer::TWeakViewModelPtr<UE::Sequencer::IOutlinerExtension>> 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 TWeakViewModelPtr<IOutlinerExtension>& WeakItem : InItems)
{
FViewModelPtr Item = WeakItem.Pin();
TSharedPtr<IGroupableExtension> Groupable = Item ? Item->FindAncestorOfType<IGroupableExtension>(true) : nullptr;
if (Groupable)
{
NodesToAdd.Add(IOutlinerExtension::GetPathName(Item));
}
}
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);
}
}
TSharedPtr<SSequencerGroupManager> NodeGroupManager = SequencerWidget->GetNodeGroupsManager();
if (NodeGroupManager)
{
NodeGroupManager->RefreshNodeGroups();
}
}
void FSequencer::ClearFilters()
{
SequencerWidget->SetSearchText(FText::GetEmpty());
FilterBar->EnableFilters(false);
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 (!ViewModel)
{
return;
}
if ( bUpdatingSequencerSelection || !IsLevelEditorSequencer() )
{
return;
}
TGuardValue<bool> Guard(bUpdatingExternalSelection, true);
TSet<AActor*> SelectedSequencerActors;
TSet<UActorComponent*> SelectedSequencerComponents;
const FName ControlRigEditModeModeName("EditMode.ControlRig");
const bool bHACK_ControlRigEditMode = GLevelEditorModeTools().GetActiveMode(ControlRigEditModeModeName) != nullptr;
for (TWeakViewModelPtr<IOutlinerExtension> WeakOutlinerItem : ViewModel->GetSelection()->Outliner.GetSelected())
{
FViewModelPtr OutlinerItem = WeakOutlinerItem.Pin();
if (!OutlinerItem)
{
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
? CastViewModel<IObjectBindingExtension>(OutlinerItem)
: OutlinerItem->FindAncestorOfType<IObjectBindingExtension>(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();
// Ensure that the (newer) typed element selection broadcasts its changes immediately so we don't get an
// end of frame update which might overwrite a newly created track area selection
if (UTypedElementSelectionSet* TypedElements = GEditor->GetSelectedActors()->GetElementSelectionSet())
{
TypedElements->NotifyPendingChanges();
}
if (SelectedSequencerComponents.Num())
{
GEditor->GetSelectedComponents()->Modify();
GEditor->GetSelectedComponents()->BeginBatchSelectOperation();
for (UActorComponent* SelectedSequencerComponent : SelectedSequencerComponents)
{
GEditor->SelectComponent(SelectedSequencerComponent, true, bNotifySelectionChanged, bSelectEvenIfHidden);
}
GEditor->GetSelectedComponents()->EndBatchSelectOperation();
GEditor->NoteSelectionChange();
// Ensure that the (newer) typed element selection broadcasts its changes immediately so we don't get an
// end of frame update which might overwrite a newly created track area selection
if (UTypedElementSelectionSet* TypedElements = GEditor->GetSelectedComponents()->GetElementSelectionSet())
{
TypedElements->NotifyPendingChanges();
}
}
}
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);
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
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->Outliner.IsSelected(ObjectModel))
{
bool bAnyChildrenSelected = false;
for (TViewModelPtr<IOutlinerExtension> Child : ObjectModel->GetDescendantsOfType<IOutlinerExtension>())
{
if (Selection->Outliner.IsSelected(Child) || Selection->NodeHasSelectedKeysOrSections(Child))
{
bAnyChildrenSelected = true;
break;
}
}
if (!bAnyChildrenSelected)
{
bAllAlreadySelected = false;
}
}
}
else if (Selection->Outliner.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->Outliner.Num())))
{
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
Selection->Outliner.Empty();
for (TSharedPtr<FObjectBindingModel> NodeToSelect : NodesToSelect)
{
Selection->Outliner.Select(NodeToSelect);
}
TSharedPtr<SOutlinerView> TreeView = SequencerWidget->GetTreeView();
bool bScrolledIntoView = false;
for (TViewModelPtr<IOutlinerExtension> Node : Selection->Outliner)
{
for (TViewModelPtr<IOutlinerExtension> Parent : Node.AsModel()->GetAncestorsOfType<IOutlinerExtension>())
{
TreeView->SetItemExpansion(Parent, true);
Parent = Parent.AsModel()->FindAncestorOfType<IOutlinerExtension>();
}
if (!bScrolledIntoView)
{
bScrolledIntoView = true;
TreeView->RequestScrollIntoView(Node);
}
}
}
}
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 FOutlinerSelection& CurrentSelection = ViewModel->GetSelection()->Outliner;
TSet<TViewModelPtr<IOutlinerExtension>> NodesToSelect;
for (TViewModelPtr<IOutlinerExtension> DisplayNode : ViewModel->GetRootModel()->GetDescendantsOfType<IOutlinerExtension>())
{
if (NodePaths.Contains(IOutlinerExtension::GetPathName(DisplayNode)))
{
NodesToSelect.Add(DisplayNode);
if (bAllAlreadySelected && !CurrentSelection.IsSelected(DisplayNode))
{
bAllAlreadySelected = false;
}
}
}
if (!bAllAlreadySelected || (NodesToSelect.Num() != CurrentSelection.Num()))
{
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
Selection->Outliner.Empty();
for (TViewModelPtr<IOutlinerExtension> NodeToSelect : NodesToSelect)
{
Selection->Outliner.Select( NodeToSelect );
}
TSharedPtr<SOutlinerView> TreeView = SequencerWidget->GetTreeView();
for (TViewModelPtr<IOutlinerExtension> Node : Selection->Outliner)
{
for (TViewModelPtr<IOutlinerExtension> Parent : Node.AsModel()->GetAncestorsOfType<IOutlinerExtension>())
{
TreeView->SetItemExpansion(Parent, true);
TreeView->SetItemExpansion(Parent, true);
Parent = Parent.AsModel()->FindAncestorOfType<IOutlinerExtension>();
}
TreeView->RequestScrollIntoView(Node);
break;
}
}
}
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 + '.';
auto RenamePathsInArray = [this, &NewPath, &PathPrefix](TArray<FString>& InArray)
{
TArray<FString> PathsToRename;
for (const FString& NodePath : InArray)
{
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))
{
InArray.Remove(NodePath);
InArray.Add(NewNodePath);
}
}
}
};
RenamePathsInArray(MovieScene->GetSoloNodes());
RenamePathsInArray(MovieScene->GetMuteNodes());
}
}
void FSequencer::OnSelectedNodesOnlyChanged()
{
RefreshTree();
SynchronizeSequencerSelectionWithExternalSelection();
}
void FSequencer::OnTimeDisplayFormatChanged()
{
using namespace UE::Sequencer;
for (TSharedRef<FSequencerNumericTypeInterface> Interface : GetNumericTypeInterfaces())
{
if (Interface->Interface->GetOnSettingChanged() != nullptr)
{
Interface->Interface->GetOnSettingChanged()->Broadcast();
}
}
}
void FSequencer::ZoomToFit()
{
using namespace UE::Sequencer;
FFrameRate TickResolution = GetFocusedTickResolution();
TRange<FFrameNumber> BoundsHull = TRange<FFrameNumber>::All();
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
for (FKeyHandle Key : Selection->KeySelection)
{
TSharedPtr<FChannelModel> Channel = Selection->KeySelection.GetModelForKey(Key);
if (Channel)
{
FFrameNumber KeyTime = Channel->GetKeyArea()->GetKeyTime(Key);
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
{
FPropertyPath PropertyPath;
return ObjectChangeListener->CanKeyProperty(CanKeyPropertyParams, PropertyPath);
}
void FSequencer::KeyProperty(FKeyPropertyParams KeyPropertyParams)
{
ObjectChangeListener->KeyProperty(KeyPropertyParams);
}
EPropertyKeyedStatus FSequencer::GetPropertyKeyedStatus(const IPropertyHandle& PropertyHandle) const
{
return PropertyKeyedStatusHandler->GetPropertyKeyedStatus(PropertyHandle);
}
FSequencerSelectionPreview& FSequencer::GetSelectionPreview()
{
return SelectionPreview;
}
void FSequencer::GetSelectedTracks(TArray<UMovieSceneTrack*>& OutSelectedTracks)
{
OutSelectedTracks.Append(ViewModel->GetSelection()->GetSelectedTracks().Array());
}
void FSequencer::GetSelectedTrackRows(TArray<TPair<UMovieSceneTrack*, int32>>& OutSelectedTrackRows)
{
OutSelectedTrackRows.Append(ViewModel->GetSelection()->GetSelectedTrackRows().Array());
}
void FSequencer::GetSelectedSections(TArray<UMovieSceneSection*>& OutSelectedSections)
{
for (TWeakObjectPtr<UMovieSceneSection> WeakSection : ViewModel->GetSelection()->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 (FViewModelPtr Node : ViewModel->GetSelection()->Outliner)
{
NodesToKey.Add(Node.AsModel().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.AsModel().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 (FKeyHandle Key : ViewModel->GetSelection()->KeySelection)
{
TSharedPtr<FChannelModel> Channel = ViewModel->GetSelection()->KeySelection.GetModelForKey(Key);
if (Channel)
{
KeyAreas.Add(Channel->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<TViewModelPtr<IOutlinerExtension>> NodesToSelect;
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
TSharedPtr<FSectionModel> SectionHandle = NodeTree->GetSectionModel(Section);
int32 Count = 0;
if (SectionHandle)
{
TSharedPtr<FViewModel> TrackNode = SectionHandle->GetParentTrackModel();
for (TViewModelPtr<FCategoryGroupModel> Node : TrackNode->GetChildrenOfType<FCategoryGroupModel>())
{
if (Count++ == Index)
{
bool bAlreadySelected = false;
if (bSelect == true)
{
bAlreadySelected = Selection->Outliner.IsSelected(Node);
}
if (bAlreadySelected == false)
{
NodesToSelect.Add(Node);
if (bSelect == false) //make sure all children not selected
{
for (TViewModelPtr<IOutlinerExtension> ChildNode : Node->GetChildrenOfType<IOutlinerExtension>())
{
NodesToSelect.Add(ChildNode);
}
}
}
}
}
}
if (bSelect)
{
if (Settings->GetAutoExpandNodesOnSelection())
{
for (FViewModelPtr DisplayNode : NodesToSelect)
{
FViewModelPtr Parent = DisplayNode->GetParent();
if (TViewModelPtr<ITrackExtension> ParentTrack = Parent.ImplicitCast())
{
if (TViewModelPtr<IOutlinerExtension> ParentOutliner = Parent.ImplicitCast())
{
if (!ParentOutliner->IsExpanded())
{
ParentOutliner->SetExpansion(true);
}
}
break;
}
}
}
if (NodesToSelect.Num() > 0)
{
SequencerWidget->GetTreeView()->RequestScrollIntoView(CastViewModel<IOutlinerExtension>(NodesToSelect[0]));
Selection->Outliner.SelectRange(NodesToSelect);
}
}
else if (NodesToSelect.Num() > 0)
{
for (const TViewModelPtr<IOutlinerExtension>& DisplayNode : NodesToSelect)
{
Selection->Outliner.Deselect(DisplayNode);
}
}
}
void FSequencer::SelectByChannels(UMovieSceneSection* Section, TArrayView<const FMovieSceneChannelHandle> InChannels, bool bSelectParentInstead, bool bSelect)
{
using namespace UE::Sequencer;
TSet<TViewModelPtr<IOutlinerExtension>> Nodes;
TArray<TViewModelPtr<IOutlinerExtension>> 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->FindAncestorOfType<IOutlinerExtension>());
}
else
{
Nodes.Add(KeyAreaNode->FindAncestorOfType<IOutlinerExtension>());
}
}
if (!bSelectParentInstead || bSelect == false)
{
// Handle top-level channels which might need to select their parent model
TViewModelPtr<IOutlinerExtension> OutlinerNode = CastViewModel<IOutlinerExtension>(KeyAreaNode);
if (!OutlinerNode)
{
OutlinerNode = KeyAreaNode->FindAncestorOfType<IOutlinerExtension>();
}
if (bSelect)
{
NodesToSelect.Add(OutlinerNode);
}
else
{
Nodes.Add(OutlinerNode);
}
}
}
}
}
}
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
if (bSelect)
{
if (NodesToSelect.Num() > 0)
{
//todo hide behind preference
SequencerWidget->GetTreeView()->RequestScrollIntoView(CastViewModel<IOutlinerExtension>(NodesToSelect[0]));
Selection->Outliner.SelectRange(NodesToSelect);
}
}
else if (Nodes.Num() > 0)
{
for (const TViewModelPtr<IOutlinerExtension>& DisplayNode : Nodes)
{
Selection->Outliner.Deselect(DisplayNode);
}
}
}
void FSequencer::SelectByChannels(UMovieSceneSection* Section, const TArray<FName>& InChannelNames, bool bSelectParentInstead, bool bSelect)
{
using namespace UE::Sequencer;
TSet<TViewModelPtr<IOutlinerExtension>> Nodes;
TArray<TViewModelPtr<IOutlinerExtension>> NodesToSelect;
TSharedPtr<FSectionModel> SectionHandle = NodeTree->GetSectionModel(Section);
if (SectionHandle)
{
TSharedPtr<FViewModel> TrackNode = SectionHandle->GetParentTrackModel();
for (TSharedPtr<FChannelGroupOutlinerModel> KeyAreaNode : TrackNode->GetDescendantsOfType<FChannelGroupOutlinerModel>())
{
TViewModelPtr<IOutlinerExtension> Parent = KeyAreaNode->FindAncestorOfType<IOutlinerExtension>();
if (Parent && InChannelNames.Contains(*Parent->GetLabel().ToString()))
{
Nodes.Add(Parent);
}
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(Parent);
}
if (!bSelectParentInstead || bSelect == false)
{
Nodes.Add(KeyAreaNode);
}
}
}
}
}
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
if (bSelect)
{
for (const TViewModelPtr<IOutlinerExtension>& DisplayNode : Nodes)
{
if (Settings->GetAutoExpandNodesOnSelection())
{
TViewModelPtr<IOutlinerExtension> Parent = DisplayNode.AsModel()->FindAncestorOfType<IOutlinerExtension>();
if (Parent && Parent.AsModel()->IsA<ITrackExtension>() && !Parent->IsExpanded())
{
Parent->SetExpansion(true);
}
}
NodesToSelect.Add(DisplayNode);
}
if (NodesToSelect.Num() > 0)
{
SequencerWidget->GetTreeView()->RequestScrollIntoView(CastViewModel<IOutlinerExtension>(NodesToSelect[0]));
Selection->Outliner.SelectRange(NodesToSelect);
}
}
else if (Nodes.Num() > 0)
{
for (const TViewModelPtr<IOutlinerExtension>& DisplayNode : Nodes)
{
Selection->Outliner.Deselect(DisplayNode);
}
}
}
void FSequencer::SelectObject(FGuid ObjectBinding)
{
using namespace UE::Sequencer;
FObjectBindingModelStorageExtension* ObjectStorage = ViewModel->GetRootModel()->CastDynamic<FObjectBindingModelStorageExtension>();
check(ObjectStorage);
if (TSharedPtr<FObjectBindingModel> Model = ObjectStorage->FindModelForObjectBinding(ObjectBinding))
{
ViewModel->GetSelection()->Outliner.Select(Model);
}
}
void FSequencer::SelectTrack(UMovieSceneTrack* Track)
{
using namespace UE::Sequencer;
FTrackModelStorageExtension* TrackStorage = ViewModel->GetRootModel()->CastDynamic<FTrackModelStorageExtension>();
check(TrackStorage);
TSharedPtr<FTrackModel> TrackModel = TrackStorage->FindModelForTrack(Track);
if (TrackModel)
{
ViewModel->GetSelection()->Outliner.Select(TrackModel);
}
}
void FSequencer::SelectSection(UMovieSceneSection* Section)
{
using namespace UE::Sequencer;
FSectionModelStorageExtension* SectionModelStorage = ViewModel->GetRootModel()->CastDynamic<FSectionModelStorageExtension>();
check(SectionModelStorage);
TSharedPtr<FSectionModel> SectionModel = SectionModelStorage->FindModelForSection(Section);
if (SectionModel)
{
ViewModel->GetSelection()->TrackArea.Select(SectionModel);
}
}
void FSequencer::SelectKey(UMovieSceneSection* InSection, TSharedPtr<UE::Sequencer::FChannelModel> InChannel, FKeyHandle KeyHandle, bool bToggle)
{
if (bToggle && ViewModel->GetSelection()->KeySelection.IsSelected(KeyHandle))
{
ViewModel->GetSelection()->KeySelection.Deselect(KeyHandle);
}
else
{
ViewModel->GetSelection()->KeySelection.Select(InChannel, KeyHandle);
}
}
void FSequencer::SelectByPropertyPaths(const TArray<FString>& InPropertyPaths)
{
using namespace UE::Sequencer;
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
Selection->Empty();
for (TViewModelPtr<ITrackExtension> Track : ViewModel->GetRootModel()->GetDescendantsOfType<ITrackExtension>())
{
if (UMovieScenePropertyTrack* PropertyTrack = Cast<UMovieScenePropertyTrack>(Track->GetTrack()))
{
FString Path = PropertyTrack->GetPropertyPath().ToString();
for (const FString& PropertyPath : InPropertyPaths)
{
if (Path == PropertyPath)
{
Selection->Outliner.Select(CastViewModelChecked<IOutlinerExtension>(Track));
break;
}
}
}
}
}
void FSequencer::SelectFolder(UMovieSceneFolder* Folder)
{
using namespace UE::Sequencer;
FFolderModelStorageExtension* FolderModelStorage = ViewModel->GetRootModel()->CastDynamic<FFolderModelStorageExtension>();
check(FolderModelStorage);
TSharedPtr<FFolderModel> FolderModel = FolderModelStorage->FindModelForFolder(Folder);
if (FolderModel)
{
ViewModel->GetSelection()->TrackArea.Select(FolderModel);
}
}
void FSequencer::EmptySelection()
{
ViewModel->GetSelection()->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 (UMovieSceneSection* SelectedSectionPtr : ViewModel->GetSelection()->GetSelectedSections())
{
if (SelectedSectionPtr && 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 (ViewModel->GetSelection()->KeySelection.Num())
{
FScopedTransaction DeleteKeysTransaction( NSLOCTEXT("Sequencer", "DeleteKeys_Transaction", "Delete Keys") );
DeleteSelectedKeys();
}
else if (ViewModel->GetSelection()->GetSelectedSections().Num())
{
FScopedTransaction DeleteSectionsTransaction( NSLOCTEXT("Sequencer", "DeleteSections_Transaction", "Delete Sections") );
DeleteSections(ViewModel->GetSelection()->GetSelectedSections());
}
else if (ViewModel->GetSelection()->Outliner.Num())
{
DeleteSelectedNodes(false);
}
}
void FSequencer::DeleteNode(TSharedRef<FViewModel> NodeToBeDeleted, const bool bKeepState)
{
using namespace UE::Sequencer;
// If this node is selected, delete all selected nodes
if (GetSelection().Outliner.IsSelected(CastViewModelChecked<IOutlinerExtension>(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;
if (ViewModel->GetSelection()->Outliner.Num() == 0)
{
return;
}
const FScopedTransaction Transaction( NSLOCTEXT("Sequencer", "UndoDeletingObject", "Delete Node") );
TArray<TSharedPtr<FViewModel>> SelectedNodesCopy;
SelectedNodesCopy.Reserve(ViewModel->GetSelection()->Outliner.Num());
for (FViewModelPtr OutlinerNode : ViewModel->GetSelection()->Outliner)
{
SelectedNodesCopy.Emplace(MoveTemp(OutlinerNode));
}
ViewModel->GetSelection()->Outliner.Empty();
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()->RemoveChildTrack(TrackNode->GetTrack());
}
DestinationFolder->AddChildTrack(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.
for (TViewModelPtr<IDraggableOutlinerExtension> DraggableModel : GetSelection().Outliner.Filter<IDraggableOutlinerExtension>())
{
// Only nodes that can be dragged can be moved in to a folder.
if (DraggableModel->CanDrag())
{
NodesToMove.Add(DraggableModel.AsModel().ToSharedRef());
}
}
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.AddUnique(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> > NodesToFolders;
for (FViewModelPtr SelectedNode : GetSelection().Outliner)
{
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.AsModel().ToSharedRef());
}
}
else if (ITrackExtension* TrackNode = SelectedNode->CastThis<ITrackExtension>())
{
if (TrackNode->GetTrack())
{
if (Folder->GetFolder()->GetChildTracks().Contains(TrackNode->GetTrack()))
{
NodesToFolders.Add(SelectedNode.AsModel().ToSharedRef());
}
}
}
}
}
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"));
ViewModel->GetSelection()->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
ViewModel->GetSelection()->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()->RemoveChildTrack(TrackNode->GetTrack());
}
}
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
TArray<TSharedPtr<FMovieSceneClipboard>> GClipboardStack;
void FSequencer::CopySelectedObjects(TArray<TSharedPtr<UE::Sequencer::FObjectBindingModel>>& ObjectNodes, const TArray<UMovieSceneFolder*>& Folders, FString& ExportedText)
{
using namespace UE::Sequencer;
// Gather guids for the object nodes and any child object nodes
TSet<FMovieSceneBindingProxy> Bindings;
for (TSharedPtr<FObjectBindingModel> ObjectNode : ObjectNodes)
{
Bindings.Add(FMovieSceneBindingProxy(ObjectNode->GetObjectGuid(), GetFocusedMovieSceneSequence()));
TSet<TSharedRef<FViewModel> > DescendantNodes;
SequencerHelpers::GetDescendantNodes(ObjectNode.ToSharedRef(), DescendantNodes);
for (auto DescendantNode : DescendantNodes)
{
if (FObjectBindingModel* DescendantObjectNode = DescendantNode->CastThis<FObjectBindingModel>())
{
Bindings.Add(FMovieSceneBindingProxy(DescendantObjectNode->GetObjectGuid(), GetFocusedMovieSceneSequence()));
}
}
}
TArray<FMovieSceneBindingProxy> BindingsArray = Bindings.Array();
FSequencerUtilities::CopyBindings(AsShared(), BindingsArray, Folders, 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;
TArray<UMovieSceneTrack*> CopyableTracks;
TArray<UMovieSceneSection*> CopyableSections;
for (TSharedPtr<FViewModel> TrackNode : TrackNodes)
{
bool bIsParentSelected = false;
TSharedPtr<FViewModel> ParentNode = TrackNode->GetParent();
while (ParentNode.IsValid() && !ParentNode->IsA<FFolderModel>())
{
if (ViewModel->GetSelection()->Outliner.IsSelected(CastViewModel<IOutlinerExtension>(ParentNode)))
{
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())
{
CopyableSections.Add(Section);
}
}
}
else if (UMovieSceneTrack* Track = TrackNode->CastThisChecked<ITrackExtension>()->GetTrack())
{
CopyableTracks.Add(Track);
}
}
}
if (CopyableSections.Num() + CopyableTracks.Num() > 0)
{
FString SectionsExportedText;
FSequencerUtilities::CopySections(CopyableSections, SectionsExportedText);
FString TracksExportedText;
FSequencerUtilities::CopyTracks(CopyableTracks, Folders, TracksExportedText);
ExportedText.Empty();
ExportedText += SectionsExportedText;
ExportedText += TracksExportedText;
// Make sure to clear the clipboard for the keys
GClipboardStack.Empty();
}
}
void FSequencer::CopySelectedFolders(const TArray<UMovieSceneFolder*>& Folders, FString& FoldersExportedText, FString& TracksExportedText, FString& ObjectExportedText)
{
FSequencerUtilities::CopyFolders(AsShared(), Folders, FoldersExportedText, TracksExportedText, ObjectExportedText);
// Make sure to clear the clipboard for the keys
GClipboardStack.Empty();
}
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 |= FSequencerUtilities::PasteFolders(TextToImport, FMovieScenePasteFoldersParams(GetFocusedMovieSceneSequence(), ParentFolder), PastedFolders, PasteErrors);
}
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::ObjectBindings))
{
EPasteResult PasteObjectBindingsResult = PasteObjectBindings(TextToImport, ParentFolder, PastedFolders, PasteErrors, bClearSelection);
if (PasteObjectBindingsResult == EPasteResult::Cancelled)
{
return false;
}
bAnythingPasted |= PasteObjectBindingsResult == EPasteResult::Success;
}
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::Tracks))
{
EPasteResult PasteTracksResult = PasteTracks(TextToImport, ParentFolder, PastedFolders, PasteErrors, bClearSelection);
if (PasteTracksResult == EPasteResult::Cancelled)
{
return false;
}
bAnythingPasted |= PasteTracksResult == EPasteResult::Success;
}
if (!bAnythingPasted)
{
if (EnumHasAnyFlags(PasteSupport, ESequencerPasteSupport::Sections))
{
EPasteResult PasteSectionsResult = PasteSections(TextToImport, PasteErrors);
if (PasteSectionsResult == EPasteResult::Cancelled)
{
return false;
}
bAnythingPasted |= PasteSectionsResult == EPasteResult::Success;
}
}
if (!bAnythingPasted)
{
for (auto NotificationInfo : PasteErrors)
{
NotificationInfo.bUseLargeFont = false;
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
}
}
return bAnythingPasted;
}
FSequencer::EPasteResult FSequencer::PasteObjectBindings(const FString& TextToImport, UMovieSceneFolder* InParentFolder, const TArray<UMovieSceneFolder*>& InFolders, TArray<FNotificationInfo>& PasteErrors, bool bClearSelection)
{
using namespace UE::Sequencer;
TArray<FMovieSceneBindingProxy> TargetBindings;
if (!bClearSelection)
{
for (TViewModelPtr<FObjectBindingModel> ObjectNode : ViewModel->GetSelection()->Outliner.Filter<FObjectBindingModel>())
{
TargetBindings.Add(FMovieSceneBindingProxy(ObjectNode->GetObjectGuid(), GetFocusedMovieSceneSequence()));
}
}
UWorld* World = GCurrentLevelEditingViewportClient ? GCurrentLevelEditingViewportClient->GetWorld() : nullptr;
bool bDuplicateExistingActors = false;
TArray<FString> ObjectNames = FSequencerUtilities::GetPasteBindingsObjectNames(AsShared(), TextToImport);
// If the current movie scene has bindings that are bound to actors with the same name of the paste buffer, prompt the user what to do
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
bool bWasPrompted = false;
for (int32 Index = 0; Index < MovieScene->GetPossessableCount() && !bWasPrompted; ++Index)
{
FMovieScenePossessable& Possessable = MovieScene->GetPossessable(Index);
FGuid ThisGuid = Possessable.GetGuid();
for (TWeakObjectPtr<> WeakObject : FindBoundObjects(ThisGuid, GetFocusedTemplateID()))
{
if (WeakObject.IsValid())
{
AActor* Actor = Cast<AActor>(WeakObject.Get());
if (Actor && ObjectNames.Contains(Actor->GetPathName(World)))
{
FText DuplicateActorsMsg = FText::Format(LOCTEXT("DuplicateActorsForPastedBinding", "Attempting to paste a binding that is already bound to {0}.\nShould the existing actor be duplicated for the pasted binding?"), FText::FromString(Actor->GetActorLabel()));
EAppReturnType::Type YesNoCancelReply = FMessageDialog::Open(EAppMsgType::YesNoCancel, DuplicateActorsMsg);
switch (YesNoCancelReply)
{
case EAppReturnType::Yes:
bDuplicateExistingActors = true;
break;
case EAppReturnType::No:
bDuplicateExistingActors = false;
break;
case EAppReturnType::Cancel:
return EPasteResult::Cancelled;
}
bWasPrompted = true;
break;
}
}
}
}
TArray<FMovieSceneBindingProxy> OutBindings;
if (FSequencerUtilities::PasteBindings(TextToImport, AsShared(), FMovieScenePasteBindingsParams(TargetBindings, InParentFolder, InFolders, bDuplicateExistingActors), OutBindings, PasteErrors))
{
return EPasteResult::Success;
}
return EPasteResult::Failure;
}
FSequencer::EPasteResult FSequencer::PasteTracks(const FString& TextToImport, UMovieSceneFolder* InParentFolder, const TArray<UMovieSceneFolder*>& InFolders, TArray<FNotificationInfo>& PasteErrors, bool bClearSelection)
{
using namespace UE::Sequencer;
TArray<FMovieSceneBindingProxy> TargetBindings;
if (!bClearSelection)
{
for (TViewModelPtr<FObjectBindingModel> ObjectNode : ViewModel->GetSelection()->Outliner.Filter<FObjectBindingModel>())
{
TargetBindings.Add(FMovieSceneBindingProxy(ObjectNode->GetObjectGuid(), GetFocusedMovieSceneSequence()));
}
}
TArray<UMovieSceneTrack*> OutTracks;
bool bSuccess = FSequencerUtilities::PasteTracks(TextToImport, FMovieScenePasteTracksParams(GetFocusedMovieSceneSequence(), TargetBindings, InParentFolder, InFolders), OutTracks, PasteErrors);
if (bSuccess)
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
return EPasteResult::Success;
}
return EPasteResult::Failure;
}
FSequencer::EPasteResult FSequencer::PasteSections(const FString& TextToImport, TArray<FNotificationInfo>& PasteErrors)
{
using namespace UE::Sequencer;
FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
TSharedPtr<FSequencerSelection> Selection = ViewModel->GetSelection();
FSelectionEventSuppressor EventSuppressor = Selection->SuppressEvents();
TArray<TSharedRef<FViewModel>> SelectedNodes;
SelectedNodes.Reserve(Selection->Outliner.Num());
for (FViewModelPtr SelectedNode : Selection->Outliner)
{
SelectedNodes.Add(SelectedNode.AsModel().ToSharedRef());
}
TSet<UMovieSceneSection*> SelectedSections = Selection->GetSelectedSections();
if (SelectedNodes.Num() == 0)
{
if (Selection->GetNodesWithSelectedKeysOrSections().Num() != 0)
{
SelectedNodes.Reserve(Selection->GetNodesWithSelectedKeysOrSections().Num());
for (TViewModelPtr<IOutlinerExtension> Item : Selection->IterateIndirectOutlinerSelection())
{
SelectedNodes.Add(Item.AsModel().ToSharedRef());
}
}
}
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 (SelectedSections.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 EPasteResult::Failure;
}
TArray<UMovieSceneTrack*> Tracks;
TArray<int32> TrackRowIndices;
for (TSharedRef<FViewModel> Node : SelectedNodes)
{
if (ITrackExtension* TrackNode = Node->CastThis<ITrackExtension>())
{
UMovieSceneTrack* TrackForNode = TrackNode->GetTrack();
if (TrackForNode)
{
Tracks.Add(TrackForNode);
if (FTrackRowModel* TrackRowNode = Node->CastThis<FTrackRowModel>())
{
TrackRowIndices.Add(TrackRowNode->GetRowIndex());
}
}
}
}
// Otherwise, look at all child nodes
if (Tracks.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;
}
if (ITrackExtension* TrackNode = DescendantNode->CastThis<ITrackExtension>())
{
UMovieSceneTrack* TrackForNode = TrackNode->GetTrack();
if (TrackForNode)
{
Tracks.Add(TrackForNode);
if (FTrackRowModel* TrackRowNode = Node->CastThis<FTrackRowModel>())
{
TrackRowIndices.Add(TrackRowNode->GetRowIndex());
}
}
}
}
}
}
TArray<UMovieSceneSection*> NewSections;
if (!FSequencerUtilities::PasteSections(TextToImport, FMovieScenePasteSectionsParams(Tracks, TrackRowIndices, GetLocalTime().Time), NewSections, PasteErrors))
{
return EPasteResult::Failure;
}
FSectionModelStorageExtension* SectionModelStorage = ViewModel->GetRootModel()->CastDynamic<FSectionModelStorageExtension>();
{
UE::MovieScene::FScopedSignedObjectModifyDefer ForceFlush(true);
check(SectionModelStorage);
}
EmptySelection();
for (UMovieSceneSection* NewSection : NewSections)
{
TSharedPtr<FSectionModel> SectionModel = SectionModelStorage->FindModelForSection(NewSection);
if (ensure(SectionModel))
{
Selection->TrackArea.Select(SectionModel);
}
}
ThrobSectionSelection();
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded);
return EPasteResult::Success;
}
bool FSequencer::CanPaste(const FString& TextToImport)
{
return (FSequencerUtilities::CanPasteBindings(AsShared(), TextToImport) ||
FSequencerUtilities::CanPasteTracks(TextToImport) ||
FSequencerUtilities::CanPasteSections(TextToImport) ||
FSequencerUtilities::CanPasteFolders(TextToImport));
}
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& InFilterName, bool bInEnabled)
{
if (const TSharedPtr<FSequencerTrackFilter> Filter = FilterBar->FindFilterByDisplayName(InFilterName.ToString()))
{
FilterBar->SetFilterEnabled(Filter.ToSharedRef(), bInEnabled, true);
}
}
bool FSequencer::IsTrackFilterEnabled(const FText& InFilterName) const
{
if (const TSharedPtr<FSequencerTrackFilter> Filter = FilterBar->FindFilterByDisplayName(InFilterName.ToString()))
{
return FilterBar->IsFilterEnabled(Filter.ToSharedRef());
}
return false;
}
TArray<FText> FSequencer::GetTrackFilterNames() const
{
return FilterBar->GetFilterDisplayNames();
}
bool FSequencer::TrackSupportsConditions(const UMovieSceneTrack* Track) const
{
if (Track)
{
if (!Track->SupportsConditions())
{
return false;
}
// Non ECS tracks don't support conditions
if (CompiledDataManager)
{
if (const FMovieSceneEvaluationTemplate* Template = CompiledDataManager->FindTrackTemplate(CompiledDataManager->FindDataID(Track->GetTypedOuter<UMovieSceneSequence>())))
{
if (Template->FindTrack(Track->GetSignature()))
{
return false;
}
}
}
return true;
}
return false;
}
void FSequencer::ToggleNodeLocked()
{
using namespace UE::Sequencer;
bool bIsLocked = !IsNodeLocked();
const FScopedTransaction Transaction( NSLOCTEXT("Sequencer", "ToggleNodeLocked", "Toggle Node Locked") );
for (FViewModelPtr OutlinerNode : ViewModel->GetSelection()->Outliner)
{
TSet<TWeakObjectPtr<UMovieSceneSection> > Sections;
SequencerHelpers::GetAllSections(OutlinerNode, Sections);
for (auto Section : Sections)
{
Section->Modify();
Section->SetIsLocked(bIsLocked);
}
}
}
bool FSequencer::IsNodeLocked() const
{
using namespace UE::Sequencer;
// Locked only if all are locked
int NumSections = 0;
for (FViewModelPtr OutlinerNode : ViewModel->GetSelection()->Outliner)
{
TSet<TWeakObjectPtr<UMovieSceneSection> > Sections;
SequencerHelpers::GetAllSections(OutlinerNode, 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 (UMovieSceneSection* Section : ViewModel->GetSelection()->GetSelectedSections())
{
// 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 (UMovieSceneSection* Section : ViewModel->GetSelection()->GetSelectedSections())
{
// We do not want to group sections that are infinite, as they should not be moveable
if (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 (UMovieSceneSection* Section : ViewModel->GetSelection()->GetSelectedSections())
{
MovieScene->UngroupSection(*Section);
}
}
bool FSequencer::CanUngroupSelectedSections() const
{
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
for (UMovieSceneSection* Section : ViewModel->GetSelection()->GetSelectedSections())
{
if (MovieScene->IsSectionInGroup(*Section))
{
return true;
}
}
return false;
}
void FSequencer::SaveSelectedNodesSpawnableState()
{
using namespace UE::Sequencer;
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction( LOCTEXT("SaveSpawnableState", "Save spawnable state") );
MovieScene->Modify();
FScopedSlowTask SlowTask(ViewModel->GetSelection()->Outliner.Num(), LOCTEXT("SaveSpawnableStateProgress", "Saving selected spawnables"));
SlowTask.MakeDialog(true);
for (TViewModelPtr<IOutlinerExtension> OutlinerSelectionNode : ViewModel->GetSelection()->Outliner)
{
SlowTask.EnterProgressFrame();
if (TViewModelPtr<IObjectBindingExtension> ObjectBindingNode = OutlinerSelectionNode.ImplicitCast())
{
if (const FMovieSceneSpawnable* Spawnable = MovieScene->FindSpawnable(ObjectBindingNode->GetObjectGuid()))
{
SpawnRegister->SaveDefaultSpawnableState(ObjectBindingNode->GetObjectGuid(), 0, ActiveTemplateIDs.Top(), GetSharedPlaybackState());
}
else if (const FMovieSceneBindingReferences* BindingReferences = Sequence->GetBindingReferences())
{
TArrayView<const FMovieSceneBindingReference> AllReferences = BindingReferences->GetReferences(ObjectBindingNode->GetObjectGuid());
for (int32 Index = 0; Index < AllReferences.Num(); ++Index)
{
if (AllReferences[Index].CustomBinding && AllReferences[Index].CustomBinding->WillSpawnObject(GetSharedPlaybackState()))
{
SpawnRegister->SaveDefaultSpawnableState(ObjectBindingNode->GetObjectGuid(), Index, ActiveTemplateIDs.Top(), GetSharedPlaybackState());
}
}
}
if (GWarn->ReceivedUserCancel())
{
break;
}
}
}
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemsChanged);
}
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)
{
FSequencerUtilities::ConvertToSpawnable(AsShared(), Possessable->GetGuid());
NotifyMovieSceneDataChanged( EMovieSceneDataChangeType::MovieSceneStructureItemsChanged );
}
}
TArray<FGuid> FSequencer::ConvertToSpawnable(FGuid Guid)
{
TArray< FMovieSceneSpawnable*> Spawnables = FSequencerUtilities::ConvertToSpawnable(AsShared(), 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 (TViewModelPtr<IObjectBindingExtension> ObjectBindingNode : ViewModel->GetSelection()->Outliner.Filter<IObjectBindingExtension>())
{
// 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.Get());
}
}
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 = FSequencerUtilities::ConvertToSpawnable(AsShared(), 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 );
}
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)
{
FSequencerUtilities::ConvertToPossessable(AsShared(), 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 (TViewModelPtr<IObjectBindingExtension> ObjectBindingNode : ViewModel->GetSelection()->Outliner.Filter<IObjectBindingExtension>())
{
if (SpawnRegister->CanConvertToPossessable(ObjectBindingNode->GetObjectGuid(), GetFocusedTemplateID(), GetSharedPlaybackState()))
{
ObjectBindingNodes.Add(ObjectBindingNode.Get());
}
}
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();
FMovieScenePossessable* Possessable = FSequencerUtilities::ConvertToPossessable(AsShared(), ObjectBindingNode->GetObjectGuid());
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);
}
}
}
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 = GetPlaybackContext()->GetWorld();
for (const FString& FileName : OpenFilenames)
{
Recorder->LoadRecordedSequencerFile(FocusedMovieSceneSequence, PlaybackContext, FileName, OnReadComplete);
}
}
}
}
void FSequencer::AddFolder()
{
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 );
}
ViewModel->GetSelection()->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 FixSortingOrders(UMovieSceneTrack* InTrack, const TArray<UMovieSceneTrack*>& Tracks)
{
// Fix the sorting orders if InTrack is going to conflict with any of the given Tracks (ie. same class and same name)
TOptional<int32> MaxSortingOrder;
for (UMovieSceneTrack* Track : Tracks)
{
if (Track != InTrack && Track->GetClass() == InTrack->GetClass() && Track->GetDisplayName().EqualTo(InTrack->GetDisplayName()))
{
if (MaxSortingOrder.IsSet())
{
MaxSortingOrder = FMath::Max(MaxSortingOrder.GetValue(), Track->GetSortingOrder());
}
else
{
MaxSortingOrder = Track->GetSortingOrder();
}
}
}
if (MaxSortingOrder.IsSet())
{
InTrack->Modify();
InTrack->SetSortingOrder(MaxSortingOrder.GetValue() + 1);
}
}
void FixSortingOrders(FMovieSceneBinding* InBinding, UMovieScene* MovieScene)
{
// Fix the sorting orders if InBinding is going to conflict with any of the existing bindings (ie. same class and same name)
TOptional<int32> MaxSortingOrder;
for (const FMovieSceneBinding& Binding : MovieScene->GetBindings())
{
if (&Binding != InBinding && Binding.GetName() == InBinding->GetName())
{
if (MaxSortingOrder.IsSet())
{
MaxSortingOrder = FMath::Max(MaxSortingOrder.GetValue(), Binding.GetSortingOrder());
}
else
{
MaxSortingOrder = Binding.GetSortingOrder();
}
}
}
if (MaxSortingOrder.IsSet())
{
InBinding->SetSortingOrder(MaxSortingOrder.GetValue() + 1);
}
}
void FSequencer::OnAddBinding(const FGuid& ObjectBinding, UMovieScene* MovieScene)
{
using namespace UE::Sequencer;
FMovieSceneBinding* Binding = MovieScene->FindBinding(ObjectBinding);
if (Binding)
{
FixSortingOrders(Binding, MovieScene);
}
NodeTree->SortAllNodesAndDescendants();
if (FilterBar->HasIsolatedTracks())
{
for (const TViewModelPtr<IObjectBindingExtension>& ObjectBindingItem : NodeTree->GetRootNode()->GetDescendantsOfType<IObjectBindingExtension>())
{
if (ObjectBinding == ObjectBindingItem->GetObjectGuid())
{
const FString NewNodePath = IOutlinerExtension::GetPathName(ObjectBindingItem);
SequencerWidget->AddNewNodePathsToIsolate({ NewNodePath });
break;
}
}
}
}
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;
}
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
FMovieSceneBinding* Binding = MovieScene->FindBinding(ObjectBinding);
if (Binding)
{
FixSortingOrders(InTrack.Get(), Binding->GetTracks());
}
else if (MovieScene->ContainsTrack(*InTrack))
{
FixSortingOrders(InTrack.Get(), MovieScene->GetTracks());
}
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(!Binding)
{
TArray<UMovieSceneFolder*> SelectedParentFolders;
CalculateSelectedFolderAndPath(SelectedParentFolders, NewNodePath);
if (SelectedParentFolders.Num() == 1)
{
SelectedParentFolders[0]->Modify();
SelectedParentFolders[0]->AddChildTrack(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();
NodeTree->SortAllNodesAndDescendants();
if (FilterBar->HasIsolatedTracks())
{
SequencerWidget->AddNewNodePathsToIsolate({ NewNodePath });
}
}
void FSequencer::CalculateSelectedFolderAndPath(TArray<UMovieSceneFolder*>& OutSelectedParentFolders, FString& OutNewNodePath)
{
using namespace UE::Sequencer;
// Check if a folder, or child of a folder is currently selected.
for (FViewModelPtr CurrentNode : ViewModel->GetSelection()->Outliner)
{
while (CurrentNode && !CurrentNode->IsA<FFolderModel>())
{
CurrentNode = CurrentNode->GetParent();
}
if (CurrentNode)
{
UMovieSceneFolder* Folder = CurrentNode->CastThisChecked<FFolderModel>()->GetFolder();
if (Folder && !OutSelectedParentFolders.Contains(Folder))
{
OutSelectedParentFolders.Add(Folder);
}
// 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);
}
}
int32 FSequencer::FindClosestPlaybackSpeed(float InPlaybackSpeed, bool bExactOnly) const
{
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
int32 NewSpeedIndex = INDEX_NONE;
float Delta = TNumericLimits<float>::Max();
for (int32 Idx = 0; Idx < PlaybackSpeeds.Num(); Idx++)
{
float NewDelta = FMath::Abs(InPlaybackSpeed - PlaybackSpeeds[Idx]);
if (NewDelta < Delta)
{
Delta = NewDelta;
NewSpeedIndex = Idx;
}
}
if (bExactOnly && Delta > UE_KINDA_SMALL_NUMBER)
{
return INDEX_NONE;
}
return NewSpeedIndex;
}
void FSequencer::SnapToClosestPlaybackSpeed()
{
float CurrentSpeed = GetPlaybackSpeed();
int32 NewSpeedIndex = FindClosestPlaybackSpeed(CurrentSpeed);
if (NewSpeedIndex != INDEX_NONE)
{
TArray<float> PlaybackSpeeds = GetPlaybackSpeeds.Execute();
CurrentSpeedIndex = NewSpeedIndex;
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->GetForceWholeFrames())
{
FQualifiedFrameTime LocalTime = GetUnwarpedLocalTime();
FFrameRate FocusedDisplayRate = GetFocusedDisplayRate();
// Snap to the focused play rate
TOptional<FFrameTime> RootPosition = RootToUnwarpedLocalTransform.Inverse().TryTransformTime(
FFrameRate::Snap(LocalTime.Time, LocalTime.Rate, FocusedDisplayRate), CurrentTimeBreadcrumbs);
if (RootPosition)
{
// Convert the root position from tick resolution time base (the output rate), to the play position input rate
FFrameTime InputPosition = ConvertFrameTime(RootPosition.GetValue(), 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);
}
RestorePlaybackSpeedAfterPlay();
OnStopDelegate.Broadcast();
}
void FSequencer::StepForward()
{
OnStepForward();
}
void FSequencer::StepBackward()
{
OnStepBackward();
}
void FSequencer::JumpForward()
{
OnStepForward(Settings->GetJumpFrameIncrement());
}
void FSequencer::JumpBackward()
{
OnStepBackward(Settings->GetJumpFrameIncrement());
}
void FSequencer::StepToNextShot()
{
if (ActiveTemplateIDs.Num() < 2)
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = MovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack)
{
return;
}
UMovieSceneSection* TargetShotSection = MovieSceneHelpers::FindNextSection(CinematicShotTrack->GetAllSections(), GetLocalTime().Time.FloorToFrame());
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;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = MovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack)
{
return;
}
TOptional<FFrameTime> CurrentTime = SubData->OuterToInnerTransform.Inverse().TryTransformTime(SubSequenceRange.GetLowerBoundValue(), CurrentTimeBreadcrumbs);
if (!CurrentTime)
{
return;
}
UMovieSceneSubSection* NextShot = Cast<UMovieSceneSubSection>(MovieSceneHelpers::FindNextSection(CinematicShotTrack->GetAllSections(), CurrentTime->FloorToFrame()));
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)
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
UMovieScene* MovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = MovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack)
{
return;
}
UMovieSceneSection* TargetShotSection = MovieSceneHelpers::FindPreviousSection(CinematicShotTrack->GetAllSections(), GetLocalTime().Time.FloorToFrame());
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;
}
UMovieScene* MovieScene = Sequence->GetMovieScene();
UMovieSceneTrack* CinematicShotTrack = MovieScene->FindTrack(UMovieSceneCinematicShotTrack::StaticClass());
if (!CinematicShotTrack)
{
return;
}
TOptional<FFrameTime> CurrentTime = SubData->OuterToInnerTransform.Inverse().TryTransformTime(SubSequenceRange.GetLowerBoundValue(), CurrentTimeBreadcrumbs);
if (!CurrentTime)
{
return;
}
UMovieSceneSubSection* PreviousShot = Cast<UMovieSceneSubSection>(MovieSceneHelpers::FindPreviousSection(CinematicShotTrack->GetAllSections(), CurrentTime->FloorToFrame()));
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->ClearCustomSortOrders();
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::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 (ViewModel->GetSelection()->Outliner.Num() > 0)
{
using namespace UE::Sequencer;
FScopedTransaction SetKeyTransaction( NSLOCTEXT("Sequencer", "SetKey_Transaction", "Set Key") );
const FFrameNumber KeyTime = GetLocalTime().Time.FrameNumber;
FAddKeyOperation::FromNodes(ViewModel->GetSelection()->Outliner.GetSelected()).Commit(KeyTime, *this);
}
}
bool FSequencer::CanSetKeyTime() const
{
return ViewModel->GetSelection()->KeySelection.Num() > 0;
}
void FSequencer::SetKeyTime()
{
using namespace UE::Sequencer;
FFrameNumber KeyTime = 0;
const FKeySelection& KeySelection = ViewModel->GetSelection()->KeySelection;
for (FKeyHandle Key : KeySelection)
{
TSharedPtr<FChannelModel> Channel = KeySelection.GetModelForKey(Key);
if (Channel)
{
KeyTime = Channel->GetKeyArea()->GetKeyTime(Key);
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)
{
using namespace UE::Sequencer;
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"));
const FKeySelection& KeySelection = ViewModel->GetSelection()->KeySelection;
for (FKeyHandle Key : KeySelection)
{
TSharedPtr<FChannelModel> Channel = KeySelection.GetModelForKey(Key);
UMovieSceneSection* Section = Channel ? Channel->GetSection() : nullptr;
if (Channel && Section)
{
if (Section->TryModify())
{
Channel->GetKeyArea()->SetKeyTime(Key, NewFrame);
bAnythingChanged = true;
Section->ExpandToFrame(NewFrame);
}
}
}
}
if (bAnythingChanged)
{
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
}
bool FSequencer::CanRekey() const
{
return ViewModel->GetSelection()->KeySelection.Num() > 0;
}
void FSequencer::Rekey()
{
using namespace UE::Sequencer;
bool bAnythingChanged = false;
FQualifiedFrameTime CurrentTime = GetLocalTime();
FScopedTransaction RekeyTransaction(NSLOCTEXT("Sequencer", "Rekey_Transaction", "Rekey"));
const FKeySelection& KeySelection = ViewModel->GetSelection()->KeySelection;
for (FKeyHandle Key : KeySelection)
{
TSharedPtr<FChannelModel> Channel = KeySelection.GetModelForKey(Key);
UMovieSceneSection* Section = Channel ? Channel->GetSection() : nullptr;
if (Channel && Section)
{
if (Section->TryModify())
{
Channel->GetKeyArea()->SetKeyTime(Key, CurrentTime.Time.FrameNumber);
bAnythingChanged = true;
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* Track : FocusedMovieScene->GetTracks())
{
if (Track && Track->DisplayOptions.bShowVerticalFrames)
{
AddVerticalFrames(VerticalFrames, Track);
}
}
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();
FSequencerMarkedFrameHelper::FindGlobalMarkedFrames(*this, GlobalMarkedFramesCache);
bGlobalMarkedFramesCached = true;
}
void FSequencer::ToggleShowMarkedFramesGlobally()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
if (FocusedMovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
const FScopedTransaction Transaction(LOCTEXT("ToggleShowMarkedFramesGlobally", "Toggle Show Marked Frames Globally"));
FocusedMovieScene->Modify();
FocusedMovieScene->ToggleGloballyShowMarkedFrames();
InvalidateGlobalMarkedFramesCache();
}
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));
}
ViewModel->GetSelection()->MarkedFrames.Empty();
}
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);
ViewModel->GetSelection()->MarkedFrames.Empty();
}
}
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();
ViewModel->GetSelection()->MarkedFrames.Empty();
}
void FSequencer::StepToNextMark()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
FFrameNumber CurrentTime = GetLocalTime().Time.RoundToFrame();
TOptional<FFrameNumber> NearestTime;
const bool bForwards = true;
int32 MarkedIndex = FocusedMovieScene->FindNextMarkedFrame(CurrentTime, bForwards);
if (MarkedIndex != INDEX_NONE)
{
NearestTime = FocusedMovieScene->GetMarkedFrames()[MarkedIndex].FrameNumber.Value;
}
TArray<FMovieSceneMarkedFrame> GlobalMarkedFrames = GetGlobalMarkedFrames();
for (const FMovieSceneMarkedFrame& GlobalMarkedFrame : GlobalMarkedFrames)
{
if (GlobalMarkedFrame.FrameNumber > CurrentTime)
{
if (!NearestTime.IsSet())
{
NearestTime = GlobalMarkedFrame.FrameNumber;
}
else if (FMath::Abs(GlobalMarkedFrame.FrameNumber - CurrentTime) < FMath::Abs(NearestTime.GetValue() - CurrentTime))
{
NearestTime = GlobalMarkedFrame.FrameNumber;
}
}
}
if (NearestTime.IsSet())
{
SetLocalTimeDirectly(NearestTime.GetValue());
}
}
void FSequencer::StepToPreviousMark()
{
UMovieSceneSequence* FocusedMovieSequence = GetFocusedMovieSceneSequence();
if (!FocusedMovieSequence)
{
return;
}
UMovieScene* FocusedMovieScene = FocusedMovieSequence->GetMovieScene();
if (!FocusedMovieScene)
{
return;
}
FFrameNumber CurrentTime = GetLocalTime().Time.FloorToFrame();
TOptional<FFrameNumber> NearestTime;
const bool bForwards = false;
int32 MarkedIndex = FocusedMovieScene->FindNextMarkedFrame(CurrentTime, bForwards);
if (MarkedIndex != INDEX_NONE)
{
NearestTime = FocusedMovieScene->GetMarkedFrames()[MarkedIndex].FrameNumber.Value;
}
TArray<FMovieSceneMarkedFrame> GlobalMarkedFrames = GetGlobalMarkedFrames();
for (const FMovieSceneMarkedFrame& GlobalMarkedFrame : GlobalMarkedFrames)
{
if (GlobalMarkedFrame.FrameNumber < CurrentTime)
{
if (!NearestTime.IsSet())
{
NearestTime = GlobalMarkedFrame.FrameNumber;
}
else if (FMath::Abs(GlobalMarkedFrame.FrameNumber - CurrentTime) < FMath::Abs(NearestTime.GetValue() - CurrentTime))
{
NearestTime = GlobalMarkedFrame.FrameNumber;
}
}
}
if (NearestTime.IsSet())
{
SetLocalTimeDirectly(NearestTime.GetValue());
}
}
bool FSequencer::AreMarkedFramesLocked() const
{
if (IsReadOnly())
{
return true;
}
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
if (FocusedMovieSceneSequence != nullptr)
{
UMovieScene* MovieScene = FocusedMovieSceneSequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
return true;
}
return MovieScene->AreMarkedFramesLocked();
}
return false;
}
void FSequencer::ToggleMarkedFramesLocked()
{
UMovieSceneSequence* FocusedMovieSceneSequence = GetFocusedMovieSceneSequence();
if ( FocusedMovieSceneSequence != nullptr )
{
UMovieScene* MovieScene = FocusedMovieSceneSequence->GetMovieScene();
if (MovieScene->IsReadOnly())
{
FSequencerUtilities::ShowReadOnlyError();
return;
}
FScopedTransaction ToggleMarkedFramesLockTransaction( NSLOCTEXT( "Sequencer", "ToggleMarkedFramesLocked", "Toggle marked frames lock" ) );
MovieScene->Modify();
MovieScene->SetMarkedFramesLocked( !MovieScene->AreMarkedFramesLocked() );
}
}
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());
}
}
void FSequencer::CopySelection()
{
using namespace UE::Sequencer;
if (ViewModel->GetSelection()->KeySelection.Num() != 0)
{
CopySelectedKeys();
}
else if (ViewModel->GetSelection()->GetSelectedSections().Num() != 0)
{
CopySelectedSections();
}
else
{
TArray<TSharedPtr<FViewModel>> TracksToCopy;
TArray<TSharedPtr<FObjectBindingModel>> ObjectsToCopy;
TArray<UMovieSceneFolder*> FoldersToCopy;
for (FViewModelPtr Node : ViewModel->GetSelection()->Outliner)
{
GatherTracksAndObjectsToCopy(Node.AsModel().ToSharedRef(), 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, ObjectsExportedText, TracksExportedText);
}
FString ExportedText;
ExportedText += ObjectsExportedText;
ExportedText += TracksExportedText;
ExportedText += FoldersExportedText;
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
}
}
void FSequencer::CutSelection()
{
if (ViewModel->GetSelection()->KeySelection.Num() != 0)
{
CutSelectedKeys();
}
else if (ViewModel->GetSelection()->GetSelectedSections().Num() != 0)
{
CutSelectedSections();
}
else
{
FScopedTransaction CutSelectionTransaction(LOCTEXT("CutSelection_Transaction", "Cut Selection"));
CopySelection();
DeleteSelectedItems();
}
}
void FSequencer::DuplicateSelection()
{
using namespace UE::Sequencer;
FScopedTransaction DuplicateSelectionTransaction(LOCTEXT("DuplicateSelection_Transaction", "Duplicate Selection"));
const bool bClearSelection = true;
const FKeySelection& KeySelection = ViewModel->GetSelection()->KeySelection;
if (KeySelection.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());
TSet<UMovieSceneSection*> ModifiedSections;
FKeySelection NewSelection;
for (FKeyHandle KeyHandle : ViewModel->GetSelection()->KeySelection)
{
TSharedPtr<FChannelModel> Channel = KeySelection.GetModelForKey(KeyHandle);
if (Channel)
{
TSharedPtr<IKeyArea> KeyArea = Channel->GetKeyArea();
UMovieSceneSection* Section = KeyArea->GetOwningSection();
bool bModified = ModifiedSections.Contains(Section);
if (!bModified)
{
bModified = Section->TryModify();
}
if (bModified)
{
ModifiedSections.Add(Section);
FKeyHandle NewKeyHandle = KeyArea->DuplicateKey(KeyHandle);
KeyArea->SetKeyTime(NewKeyHandle, KeyArea->GetKeyTime(KeyHandle) + FrameOffset);
NewSelection.Select(Channel, NewKeyHandle);
}
}
}
ViewModel->GetSelection()->KeySelection.ReplaceWith(MoveTemp(NewSelection));
NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged);
}
else if (ViewModel->GetSelection()->GetSelectedSections().Num() != 0)
{
CopySelection();
DoPaste(bClearSelection);
}
else
{
CopySelection();
DoPaste(bClearSelection);
SynchronizeSequencerSelectionWithExternalSelection();
}
}
void FSequencer::CopySelectedKeys()
{
using namespace UE::Sequencer;
FMovieSceneClipboardBuilder Builder;
// Map selected keys to their key areas
TMap<TSharedPtr<FChannelModel>, TArray<FKeyHandle>> KeyAreaMap;
for (FKeyHandle Key : ViewModel->GetSelection()->KeySelection)
{
TSharedPtr<FChannelModel> Channel = ViewModel->GetSelection()->KeySelection.GetModelForKey(Key);
if (Channel)
{
KeyAreaMap.FindOrAdd(Channel).Add(Key);
}
}
// 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(TOptional<FFrameNumber>())) );
Clipboard->GetEnvironment().TickResolution = GetFocusedTickResolution();
Clipboard->GetEnvironment().TimeTransform = GetFocusedMovieSceneSequenceTransform().LinearTransform;
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<UMovieSceneSection*> SelectedSections;
for (TWeakObjectPtr<UMovieSceneSection> SelectedSectionPtr : ViewModel->GetSelection()->GetSelectedSections())
{
if (SelectedSectionPtr.IsValid())
{
SelectedSections.Add(SelectedSectionPtr.Get());
}
}
FString ExportedText;
FSequencerUtilities::CopySections(SelectedSections, 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(ViewModel->GetSelection()->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::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);
const FMovieSceneBindingReferences* Refs = FocusedSequence->GetBindingReferences();
for (int32 Index = 0; Index < FocusedMovieScene->GetPossessableCount(); Index++)
{
const FMovieScenePossessable& Possessable = FocusedMovieScene->GetPossessable(Index);
// Skip custom bindings here
if (Refs && Refs->GetCustomBinding(Possessable.GetGuid(), 0))
{
continue;
}
TArray<UObject*, TInlineAllocator<1>>& References = AllObjects.FindOrAdd(Possessable.GetGuid());
PRAGMA_DISABLE_DEPRECATION_WARNINGS
TArrayView<TWeakObjectPtr<>> BoundObjects = State.FindBoundObjects(Possessable.GetGuid(), GetFocusedTemplateID(), GetSharedPlaybackState());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
for (TWeakObjectPtr<> WeakObj : BoundObjects)
{
if (UObject* Obj = WeakObj.Get())
{
References.Add(Obj);
}
}
}
for (auto& Pair : AllObjects)
{
// Only rebind things if they exist
if (Pair.Value.Num() > 0)
{
FocusedSequence->UnbindPossessableObjects(Pair.Key);
for (UObject* Object : Pair.Value)
{
UObject* BindingContext = PlaybackContext;
// Find this object's parent object, if it has one.
UObject* ParentObject = FocusedSequence->GetParentObject(Object);
if (ParentObject)
{
BindingContext = ParentObject;
}
FocusedSequence->BindPossessableObject(Pair.Key, *Object, BindingContext);
}
}
}
}
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(ViewModel->GetSelection()->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 (ViewModel->GetSelection()->Outliner.Num() > 0)
{
for (FViewModelPtr Node : ViewModel->GetSelection()->Outliner)
{
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->GetTracks())
{
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(ViewModel->GetSelection()->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.JumpToStartViewport,
FExecuteAction::CreateSP(this, &FSequencer::JumpToStart));
SequencerCommandBindings->MapAction(
Commands.JumpToEndViewport,
FExecuteAction::CreateSP(this, &FSequencer::JumpToEnd));
SequencerCommandBindings->MapAction(
Commands.StepToNextKey,
FExecuteAction::CreateLambda([this] { JumpToNextKey(); }));
SequencerCommandBindings->MapAction(
Commands.StepToPreviousKey,
FExecuteAction::CreateLambda([this] { JumpToPreviousKey(); }));
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.SortAllNodesAndDescendants,
FExecuteAction::CreateSP(this, &FSequencer::SortAllNodesAndDescendants));
SequencerCommandBindings->MapAction(
Commands.ToggleShowMarkedFrames,
FExecuteAction::CreateLambda([this] { Settings->SetShowMarkedFrames(!Settings->GetShowMarkedFrames()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetShowMarkedFrames(); }));
SequencerCommandBindings->MapAction(
Commands.ToggleShowMarkedFramesGlobally,
FExecuteAction::CreateSP(this, &FSequencer::ToggleShowMarkedFramesGlobally),
FCanExecuteAction::CreateLambda([this] { return GetFocusedMovieSceneSequence() != nullptr; }),
FIsActionChecked::CreateLambda([this] { return GetFocusedMovieSceneSequence()->GetMovieScene()->GetGloballyShowMarkedFrames(); }) );
SequencerCommandBindings->MapAction(
Commands.ClearGlobalMarkedFrames,
FExecuteAction::CreateSP(this, &FSequencer::ClearGlobalMarkedFrames));
SequencerCommandBindings->MapAction(
Commands.ToggleAutoExpandNodesOnSelection,
FExecuteAction::CreateLambda([this] { Settings->SetAutoExpandNodesOnSelection(!Settings->GetAutoExpandNodesOnSelection()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetAutoExpandNodesOnSelection(); }) );
SequencerCommandBindings->MapAction(
Commands.ToggleRestoreOriginalViewportOnCameraCutUnlock,
FExecuteAction::CreateLambda([this] { Settings->SetRestoreOriginalViewportOnCameraCutUnlock(!Settings->GetRestoreOriginalViewportOnCameraCutUnlock()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetRestoreOriginalViewportOnCameraCutUnlock(); }) );
SequencerCommandBindings->MapAction(
Commands.TogglePreviewCameraCutsInSimulate,
FExecuteAction::CreateLambda([this] { TracksSettings->SetPreviewCameraCutsInSimulate(!TracksSettings->GetPreviewCameraCutsInSimulate()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return TracksSettings->GetPreviewCameraCutsInSimulate(); }) );
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.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) );
SequencerCommandBindings->MapAction(
Commands.TrimSectionLeft,
FExecuteAction::CreateSP( this, &FSequencer::TrimSection, true ),
FCanExecuteAction::CreateLambda([this] { return MovieSceneToolHelpers::CanTrimSectionLeft(this->ViewModel->GetSelection()->GetSelectedSections(), GetLocalTime()); }));
SequencerCommandBindings->MapAction(
Commands.TrimSectionRight,
FExecuteAction::CreateSP( this, &FSequencer::TrimSection, false ),
FCanExecuteAction::CreateLambda([this] { return MovieSceneToolHelpers::CanTrimSectionRight(this->ViewModel->GetSelection()->GetSelectedSections(), GetLocalTime()); }));
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([this] { return MovieSceneToolHelpers::CanSplitSection(this->ViewModel->GetSelection()->GetSelectedSections(), GetLocalTime()); }));
// 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 (FViewModelPtr Possessable : ViewModel->GetSelection()->Outliner.Filter<FPossessableModel>())
{
FViewModelPtr Parent = Possessable->GetParent();
if (!Parent || !Parent->IsA<IObjectBindingExtension>())
{
return true;
}
}
return false;
};
auto CanSelectedNodesBeConvertedToPossessables = [this] {
UMovieScene* MovieScene = GetFocusedMovieSceneSequence()->GetMovieScene();
for (TViewModelPtr<IObjectBindingExtension> ObjectBindingNode : ViewModel->GetSelection()->Outliner.Filter<IObjectBindingExtension>())
{
if (SpawnRegister->CanConvertToPossessable(ObjectBindingNode->GetObjectGuid(), GetFocusedTemplateID(), GetSharedPlaybackState()))
{
return true;
}
}
return false;
};
auto CanBrowseToObject = [this] {
for (TViewModelPtr<FSectionModel> SectionModel : ViewModel->GetSelection()->TrackArea.Filter<FSectionModel>())
{
if (UMovieSceneSection* Section = SectionModel->GetSection())
{
if (UObject* ObjectToFocus = Section->GetSourceObject())
{
return true;
}
}
}
return false;
};
SequencerCommandBindings->MapAction(
FSequencerCommands::Get().ConvertToPossessable,
FExecuteAction::CreateSP(this, &FSequencer::ConvertSelectedNodesToPossessables),
FCanExecuteAction::CreateLambda(CanSelectedNodesBeConvertedToPossessables)
);
auto AreSpawnablesSelected = [this] {
UMovieSceneSequence* MovieSceneSequence = GetFocusedMovieSceneSequence();
for (TViewModelPtr<IObjectBindingExtension> ObjectBindingNode : ViewModel->GetSelection()->Outliner.Filter<IObjectBindingExtension>())
{
if (MovieSceneHelpers::IsBoundToAnySpawnable(MovieSceneSequence, ObjectBindingNode->GetObjectGuid(), GetSharedPlaybackState()))
{
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.ToggleMarksLocked,
FExecuteAction::CreateSP(this, &FSequencer::ToggleMarkedFramesLocked),
FCanExecuteAction::CreateLambda( [this] { return GetFocusedMovieSceneSequence() != nullptr; } ),
FIsActionChecked::CreateSP(this, &FSequencer::AreMarkedFramesLocked));
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.BrowseToObject,
FExecuteAction::CreateSP( this, &FSequencer::BrowseToObject),
FCanExecuteAction::CreateLambda(CanBrowseToObject));
SequencerCommandBindings->MapAction(
Commands.ToggleLayerBars,
FExecuteAction::CreateLambda( [this]{
Settings->SetShowLayerBars( !Settings->GetShowLayerBars() );
RefreshUI();
} ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowLayerBars(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleKeyBars,
FExecuteAction::CreateLambda( [this]{
Settings->SetShowKeyBars( !Settings->GetShowKeyBars() );
RefreshUI();
} ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowKeyBars(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleChannelColors,
FExecuteAction::CreateLambda( [this]{
Settings->SetShowChannelColors( !Settings->GetShowChannelColors() );
} ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetShowChannelColors(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleShowInfoButton,
FExecuteAction::CreateLambda([this] {
Settings->SetShowInfoButton(!Settings->GetShowInfoButton());
}),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetShowInfoButton(); }));
SequencerCommandBindings->MapAction(
Commands.ChangeTimeDisplayFormat,
FExecuteAction::CreateLambda( [this]{
const UEnum* FrameNumberDisplayEnum = StaticEnum<EFrameNumberDisplayFormats>();
check(FrameNumberDisplayEnum);
uint8 NextIndex = (uint8)Settings->GetTimeDisplayFormat() + 1;
if (NextIndex >= FrameNumberDisplayEnum->NumEnums() - 1)
{
NextIndex = 0;
}
while (FrameNumberDisplayEnum->HasMetaData(TEXT("Hidden"), NextIndex))
{
NextIndex++;
if (NextIndex >= FrameNumberDisplayEnum->NumEnums() - 1)
{
NextIndex = 0;
}
}
EFrameNumberDisplayFormats NextFormat = (EFrameNumberDisplayFormats)(NextIndex);
// 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.ToggleSnapKeyTimesToElements,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapKeyTimesToElements( !Settings->GetSnapKeyTimesToElements() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapKeyTimesToElements(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleSnapSectionTimesToElements,
FExecuteAction::CreateLambda( [this]{ Settings->SetSnapSectionTimesToElements( !Settings->GetSnapSectionTimesToElements() ); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return Settings->GetSnapSectionTimesToElements(); } ) );
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.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.ToggleForceWholeFrames,
FExecuteAction::CreateLambda([this] { Settings->SetForceWholeFrames(!Settings->GetForceWholeFrames()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->GetForceWholeFrames(); }));
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<TWeakViewModelPtr<IOutlinerExtension>> SelectedNodes = ViewModel->GetSelection()->GetNodesWithSelectedKeysOrSections();
// If this is empty then we are selecting display nodes
if (SelectedNodes.Num() == 0)
{
SelectedNodes = ViewModel->GetSelection()->Outliner.GetSelected();
for (TWeakViewModelPtr<IOutlinerExtension> WeakNode : SelectedNodes)
{
FViewModelPtr 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 (FKeyHandle Key : ViewModel->GetSelection()->KeySelection)
{
TSharedPtr<FChannelModel> Channel = ViewModel->GetSelection()->KeySelection.GetModelForKey(Key);
if (!Channel)
{
continue;
}
if (!Track)
{
Track = Channel->GetSection()->GetTypedOuter<UMovieSceneTrack>();
}
if (!Track || Track != Channel->GetSection()->GetTypedOuter<UMovieSceneTrack>())
{
return false;
}
}
return true;
};
auto CanDelete = [this]{
TSharedPtr<FSequencerSelection> Selection = this->ViewModel->GetSelection();
return Selection->KeySelection.Num() || Selection->GetSelectedSections().Num() || Selection->Outliner.Num();
};
auto CanDuplicate = [this]{
TSharedPtr<FSequencerSelection> Selection = this->ViewModel->GetSelection();
if (Selection->KeySelection.Num() || Selection->GetSelectedSections().Num() || Selection->GetSelectedTracks().Num())
{
return true;
}
// For duplicate object tracks
if (Selection->GetNodesWithSelectedKeysOrSections().Num() == 0)
{
// if contains one node that can be copied we allow the action
for (TViewModelPtr<IObjectBindingExtension> Binding : Selection->Outliner.Filter<IObjectBindingExtension>())
{
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]
{
TSharedPtr<FSequencerSelection> Selection = this->ViewModel->GetSelection();
for (TViewModelPtr<IRenameableExtension> Renamable : Selection->Outliner.Filter<IRenameableExtension>())
{
if (Renamable->CanRename())
{
Renamable->OnRenameRequested().Broadcast();
}
}
}),
FCanExecuteAction::CreateLambda([this]
{
TSharedPtr<FSequencerSelection> Selection = this->ViewModel->GetSelection();
for (TViewModelPtr<IRenameableExtension> Renamable : Selection->Outliner.Filter<IRenameableExtension>())
{
if (Renamable->CanRename())
{
return true;
}
}
return false;
})
);
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.ToggleDynamicWeighting,
FExecuteAction::CreateLambda( [this]{ this->ToggleDynamicWeighting(); } ),
FCanExecuteAction::CreateLambda( []{ return true; } ),
FIsActionChecked::CreateLambda( [this]{ return this->UsesDynamicWeighting(); } ) );
SequencerCommandBindings->MapAction(
Commands.ToggleKeepCursorInPlaybackRangeWhileScrubbing,
FExecuteAction::CreateLambda([this] { Settings->SetKeepCursorInPlayRangeWhileScrubbing(!Settings->ShouldKeepCursorInPlayRangeWhileScrubbing()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->ShouldKeepCursorInPlayRangeWhileScrubbing(); }));
SequencerCommandBindings->MapAction(
Commands.ToggleResetPlayheadWhenNavigating,
FExecuteAction::CreateLambda([this] { Settings->SetResetPlayheadWhenNavigating(!Settings->ShouldResetPlayheadWhenNavigating()); }),
FCanExecuteAction::CreateLambda([] { return true; }),
FIsActionChecked::CreateLambda([this] { return Settings->ShouldResetPlayheadWhenNavigating(); }));
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 );
} ),
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::CreateLambda([this]{
const bool bSpawnable = Settings->GetCreateSpawnableCameras() && GetFocusedMovieSceneSequence()->AllowsSpawnableObjects();
ACineCameraActor* OutActor;
FSequencerUtilities::CreateCamera(AsShared(), bSpawnable, OutActor);
} ),
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.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.SetKeyTime,
FExecuteAction::CreateSP(this, &FSequencer::SetKeyTime),
FCanExecuteAction::CreateSP(this, &FSequencer::CanSetKeyTime));
SequencerCommandBindings->MapAction(
Commands.Rekey,
FExecuteAction::CreateSP(this, &FSequencer::Rekey),
FCanExecuteAction::CreateSP(this, &FSequencer::CanRekey));
SequencerCommandBindings->MapAction(
Commands.SnapToFrame,
FExecuteAction::CreateSP(this, &FSequencer::SnapToFrame),
FCanExecuteAction::CreateSP(this, &FSequencer::CanSnapToFrame));
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().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(
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.SetInterpolationCubicSmartAuto,
FExecuteAction::CreateSP(this, &FSequencer::SetInterpTangentMode, ERichCurveInterpMode::RCIM_Cubic, ERichCurveTangentMode::RCTM_SmartAuto));
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.SelectNone,
FExecuteAction::CreateSP(this, &FSequencer::EmptySelection));
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.FocusPlaybackTime,
FExecuteAction::CreateSP(this, &FSequencer::FocusPlaybackTime));
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::IsViewingRootSequence ) );
SequencerCommandBindings->MapAction(
Commands.SetSelectionRangeToPreviousShot,
FExecuteAction::CreateSP( this, &FSequencer::SetSelectionRangeToShot, false ),
FCanExecuteAction::CreateSP( this, &FSequencer::IsViewingRootSequence ) );
SequencerCommandBindings->MapAction(
Commands.SetPlaybackRangeToAllShots,
FExecuteAction::CreateSP( this, &FSequencer::SetPlaybackRangeToAllShots ),
FCanExecuteAction::CreateSP( this, &FSequencer::IsViewingRootSequence ) );
SequencerCommandBindings->MapAction(
Commands.RefreshUI,
FExecuteAction::CreateSP( this, &FSequencer::RefreshUI));
SequencerCommandBindings->MapAction(
Commands.ToggleLimitViewportSelection,
FExecuteAction::CreateSP(this, &FSequencer::ToggleLimitViewportSelection),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FSequencer::IsViewportSelectionLimited));
if (HostCapabilities.bSupportsSidebar)
{
SequencerCommandBindings->MapAction(
Commands.ToggleSidebarVisible,
FExecuteAction::CreateSP(this, &FSequencer::ToggleSidebar),
FCanExecuteAction(),
FIsActionChecked::CreateSP(this, &FSequencer::IsSidebarVisible));
}
SequencerCommandBindings->MapAction(
Commands.ToggleSidebarSelectionDrawerOpen,
FExecuteAction::CreateSP(this, &FSequencer::ToggleSidebarSelectionDrawer));
SequencerCommandBindings->MapAction(
Commands.ToggleSidebarDrawerDock,
FExecuteAction::CreateSP(this, &FSequencer::ToggleSidebarDrawerDocked));
SequencerCommandBindings->MapAction(
Commands.AlignSelectionToPlayhead,
FExecuteAction::CreateSP(this, &FSequencer::AlignSelectionToPlayhead),
FCanExecuteAction::CreateSP(this, &FSequencer::CanAlignSelectionToPlayhead));
SequencerCommandBindings->MapAction(
Commands.ToggleTrackSelectionPin,
FExecuteAction::CreateSP(this, &FSequencer::ToggleTrackSelectionPin));
SequencerCommandBindings->MapAction(
Commands.ToggleTrackSelectionLock,
FExecuteAction::CreateSP(this, &FSequencer::ToggleTrackSelectionLock));
SequencerCommandBindings->MapAction(
Commands.ToggleTrackSelectionDeactive,
FExecuteAction::CreateSP(this, &FSequencer::ToggleTrackSelectionDeactive));
SequencerCommandBindings->MapAction(
Commands.ToggleTrackSelectionMute,
FExecuteAction::CreateSP(this, &FSequencer::ToggleTrackSelectionMute));
SequencerCommandBindings->MapAction(
Commands.ToggleTrackSelectionSolo,
FExecuteAction::CreateSP(this, &FSequencer::ToggleTrackSelectionSolo));
// If this sequencer supports a curve editor, let's add bindings for it.
FCurveEditorExtension* CurveEditorExtension = ViewModel->CastDynamic<FCurveEditorExtension>();
if (CurveEditorExtension && ensure(CurveEditorExtension->GetCurveEditor()))
{
// 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.JumpToStartViewport, *SequencerCommandBindings->GetActionForCommand(Commands.JumpToStartViewport));
CurveEditorSharedBindings->MapAction(Commands.JumpToEndViewport, *SequencerCommandBindings->GetActionForCommand(Commands.JumpToEndViewport));
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.StepToNextMark, *SequencerCommandBindings->GetActionForCommand(Commands.StepToNextMark));
CurveEditorSharedBindings->MapAction(Commands.StepToPreviousMark, *SequencerCommandBindings->GetActionForCommand(Commands.StepToPreviousMark));
CurveEditorSharedBindings->MapAction(Commands.ToggleMarkAtPlayPosition, *SequencerCommandBindings->GetActionForCommand(Commands.ToggleMarkAtPlayPosition));
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));
TSharedPtr<FCurveEditor> CurveEditor = CurveEditorExtension->GetCurveEditor();
CurveEditor->GetCommands()->Append(CurveEditorSharedBindings);
}
// bind widget specific commands
SequencerWidget->BindCommands(SequencerCommandBindings, CurveEditorSharedBindings);
FilterBar->BindCommands();
}
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(), TEXT("ContentBrowser.AssetTreeFolderOpen")),
FUIAction(FExecuteAction::CreateRaw(this, &FSequencer::OnLoadRecordedData)));
}
MenuBuilder.AddMenuEntry(
LOCTEXT("AddFolder", "Add Folder"),
LOCTEXT("AddFolderToolTip", "Adds a new folder track."),
FSlateIcon(FAppStyle::GetAppStyleSetName(), TEXT("ContentBrowser.AssetTreeFolderOpen")),
FUIAction(FExecuteAction::CreateRaw(this, &FSequencer::AddFolder)));
TArray<TSharedPtr<ISequencerTrackEditor>> SortedAndFilteredTrackEditors = TrackEditors;
// Filter unsupported
for (int32 Index = SortedAndFilteredTrackEditors.Num()-1; Index >= 0; --Index)
{
if (!SortedAndFilteredTrackEditors[Index]->SupportsSequence(GetFocusedMovieSceneSequence()))
{
SortedAndFilteredTrackEditors.RemoveAtSwap(Index, 1, EAllowShrinking::No);
}
}
// Sort by name
SortedAndFilteredTrackEditors.Sort([](const TSharedPtr<ISequencerTrackEditor>& InA, const TSharedPtr<ISequencerTrackEditor>& InB)
{
return InA->GetDisplayName().CompareTo(InB->GetDisplayName()) < 0;
});
for (TSharedPtr<ISequencerTrackEditor> TrackEditor : SortedAndFilteredTrackEditors)
{
TrackEditor->BuildPinnedAddTrackMenu(MenuBuilder);
}
MenuBuilder.AddMenuSeparator(NAME_None);
for (const TSharedPtr<ISequencerTrackEditor>& TrackEditor : SortedAndFilteredTrackEditors)
{
TrackEditor->BuildAddTrackMenu(MenuBuilder);
}
}
void FSequencer::BuildAddObjectBindingsMenu(FMenuBuilder& MenuBuilder)
{
TArray<TSharedPtr<ISequencerEditorObjectBinding>> SortedObjectBindings = ObjectBindings;
SortedObjectBindings.Sort([](const TSharedPtr<ISequencerEditorObjectBinding>& InA, const TSharedPtr<ISequencerEditorObjectBinding>& InB) -> bool
{
return InA->GetDisplayName().CompareTo(InB->GetDisplayName()) < 0;
});
for (const TSharedPtr<ISequencerEditorObjectBinding>& ObjectBinding : SortedObjectBindings)
{
if (ObjectBinding->SupportsSequence(GetFocusedMovieSceneSequence()))
{
ObjectBinding->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::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 (TViewModelPtr<FFolderModel> FolderNode : ViewModel->GetSelection()->Outliner.Filter<FFolderModel>())
{
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::BuildSortMenu(FMenuBuilder& MenuBuilder)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("SortByStartTimeAscending", "Start Time Ascending"),
LOCTEXT("SortByStartTimeAscendingTooltip", "Sort the selected tracks by start time of the first layer bar ascending"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), TEXT("Profiler.Misc.SortAscending")),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::SortSelectedOutlinerItems, false, false)));
MenuBuilder.AddMenuEntry(
LOCTEXT("SortByStartTimeDescending", "Start Time Descending"),
LOCTEXT("SortByStartTimeDescendingTooltip", "Sort the selected tracks by start time of the first layer bar descending"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), TEXT("Profiler.Misc.SortDescending")),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::SortSelectedOutlinerItems, false, true)));
MenuBuilder.AddMenuSeparator();
MenuBuilder.AddMenuEntry(
LOCTEXT("SortBySelectionAscending", "Selection Order Ascending"),
LOCTEXT("SortBySelectionAscendingTooltip", "Sort the selected tracks by the order selected ascending"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), TEXT("Profiler.Misc.SortAscending")),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::SortSelectedOutlinerItems, true, false)));
MenuBuilder.AddMenuEntry(
LOCTEXT("SortBySelectionDescending", "Selection Order Descending"),
LOCTEXT("SortBySelectionDescendingTooltip", "Sort the selected tracks by the order selected descending"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), TEXT("Profiler.Misc.SortDescending")),
FUIAction(FExecuteAction::CreateSP(this, &FSequencer::SortSelectedOutlinerItems, true, true)));
}
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);
}
void FSequencer::SetSectionColorTint(TArray<UMovieSceneSection*> Sections, FColor ColorTint)
{
FScopedTransaction Transaction(LOCTEXT("SetSectionColorTint", "Set Section Color Tint"));
for (UMovieSceneSection* Section : Sections)
{
Section->Modify();
Section->SetColorTint(ColorTint);
}
}
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 (TViewModelPtr<FObjectBindingModel> ObjectBindingNode : ViewModel->GetSelection()->Outliner.Filter<FObjectBindingModel>())
{
FGuid Guid = ObjectBindingNode->GetObjectGuid();
if (Guid == Binding)
{
ObjectBindingNode->Rename(InDisplayName);
break;
}
}
}
FText FSequencer::GetDisplayName(FGuid Binding)
{
using namespace UE::Sequencer;
for (TViewModelPtr<FObjectBindingModel> ObjectBindingNode : ViewModel->GetSelection()->Outliner.Filter<FObjectBindingModel>())
{
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);
}
void FSequencer::ToggleDynamicWeighting()
{
UMovieSceneSequence* Sequence = GetFocusedMovieSceneSequence();
EMovieSceneSequenceFlags NewFlags = Sequence->GetFlags();
NewFlags ^= EMovieSceneSequenceFlags::DynamicWeighting;
FScopedTransaction Transaction(EnumHasAnyFlags(NewFlags, EMovieSceneSequenceFlags::DynamicWeighting) ? LOCTEXT("DisableDynamicWeighting", "Disable Dynamic Weighting") : LOCTEXT("EnableDynamicWeighting", "Enable Dynamic Weighting"));
Sequence->Modify();
Sequence->SetSequenceFlags(NewFlags);
}
bool FSequencer::UsesDynamicWeighting()
{
return EnumHasAnyFlags(GetFocusedMovieSceneSequence()->GetFlags(), EMovieSceneSequenceFlags::DynamicWeighting);
}
UE::Sequencer::FSequencerSelection& FSequencer::GetSelection()
{
return *ViewModel->GetSelection();
}
bool FSequencer::ShouldRestoreEditorViewports()
{
return Settings->GetRestoreOriginalViewportOnCameraCutUnlock();
}
float FSequencer::GetCameraBlendPlayRate()
{
return PlaybackSpeed;
}
void FSequencer::OnCameraCutUpdated(const UE::MovieScene::FOnCameraCutUpdatedParams& Params)
{
LastViewTargetCamera = Params.ViewTargetCamera;
OnCameraCutEvent.Broadcast(Params.ViewTarget, Params.bIsJumpCut);
}
void FSequencer::ToggleLimitViewportSelection()
{
bSelectionLimited = !bSelectionLimited;
SetViewportSelectionLimited(bSelectionLimited);
}
bool FSequencer::IsViewportSelectionLimited() const
{
return bSelectionLimited;
}
void FSequencer::SetViewportSelectionLimited(const bool bInSelectionLimited)
{
bSelectionLimited = bInSelectionLimited;
if (FSequencerEdMode* const SequencerEdMode = (FSequencerEdMode*)GLevelEditorModeTools().GetActiveMode(FSequencerEdMode::EM_SequencerMode))
{
SequencerEdMode->EnableSelectabilityTool(bSelectionLimited);
}
OnSelectionLimitedChangedDelegate.Broadcast(bSelectionLimited);
}
bool FSequencer::IsObjectSelectableInViewport(UObject* const InObject)
{
if (!bSelectionLimited)
{
return true;
}
if (const IViewportSelectableObject* const SelectableObject = Cast<IViewportSelectableObject>(InObject))
{
return SelectableObject->IsSelectable();
}
UMovieSceneSequence* const FocusedSequence = GetFocusedMovieSceneSequence();
if (!IsValid(FocusedSequence))
{
return true;
}
const TSharedRef<UE::MovieScene::FSharedPlaybackState> SharedPlaybackState = GetSharedPlaybackState();
FMovieSceneEvaluationState* const EvaluationState = SharedPlaybackState->FindCapability<FMovieSceneEvaluationState>();
if (!EvaluationState)
{
return true;
}
const UMovieSceneSequence* OutSequence = nullptr;
// Early out on first sequence the object is found in
ForEachSubSequenceRecursively(FocusedSequence,
[this, InObject, &SharedPlaybackState, EvaluationState, &OutSequence](UMovieSceneSequence* const InCurrentSequence)
{
const FMovieSceneSequenceID SequenceID = EvaluationState->FindSequenceId(InCurrentSequence);
const FGuid ObjectGuid = EvaluationState->FindCachedObjectId(*InObject, SequenceID, SharedPlaybackState);
if (ObjectGuid.IsValid())
{
OutSequence = InCurrentSequence;
return false; // Stop looping recursively
}
return true; // Continue loop recursively
});
return IsValid(OutSequence);
}
void FSequencer::ForEachSubSequenceRecursively(UMovieSceneSequence* const InSequence, const TFunctionRef<bool(UMovieSceneSequence* const InCurrentSequence)>& InFunction)
{
if (!IsValid(InSequence) || !InFunction(InSequence))
{
return;
}
UMovieScene* const MovieScene = InSequence->GetMovieScene();
if (!IsValid(MovieScene))
{
return;
}
// Converting to TSet as GetAllSections() seems to return multiples of the same object
const TSet<UMovieSceneSection*> AllSections = TSet<UMovieSceneSection*>(MovieScene->GetAllSections());
for (UMovieSceneSection* const Section : AllSections)
{
UMovieSceneSubSection* const SubSection = Cast<UMovieSceneSubSection>(Section);
if (!IsValid(SubSection))
{
continue;
}
UMovieSceneSequence* const Sequence = SubSection->GetSequence();
if (!IsValid(Sequence) || !InFunction(Sequence))
{
continue;
}
ForEachSubSequenceRecursively(Sequence, InFunction);
}
}
ISequencer::FOnViewportSelectionLimitedChanged& FSequencer::OnViewportSelectionLimitedChanged()
{
return OnSelectionLimitedChangedDelegate;
}
bool FSequencer::RegisterDrawer(FSidebarDrawerConfig&& InDrawerConfig)
{
if (SequencerWidget.IsValid())
{
return SequencerWidget->RegisterDrawer(MoveTemp(InDrawerConfig));
}
return false;
}
bool FSequencer::UnregisterDrawer(const FName InDrawerId)
{
if (SequencerWidget.IsValid())
{
return SequencerWidget->UnregisterDrawer(InDrawerId);
}
return false;
}
bool FSequencer::RegisterDrawerSection(const FName InDrawerId, const TSharedPtr<ISidebarDrawerContent>& InSection)
{
if (SequencerWidget.IsValid())
{
return SequencerWidget->RegisterDrawerSection(InDrawerId, InSection);
}
return false;
}
bool FSequencer::UnregisterDrawerSection(const FName InDrawerId, const FName InSectionId)
{
if (SequencerWidget.IsValid())
{
return SequencerWidget->UnregisterDrawerSection(InDrawerId, InSectionId);
}
return false;
}
void FSequencer::ToggleSidebar()
{
if (SequencerWidget.IsValid())
{
return SequencerWidget->ToggleSidebarVisible();
}
}
bool FSequencer::IsSidebarVisible() const
{
return SequencerWidget.IsValid() ? SequencerWidget->IsSidebarVisible() : false;
}
void FSequencer::ToggleSidebarSelectionDrawer()
{
if (SequencerWidget.IsValid())
{
SequencerWidget->ToggleSidebarSelectionDrawerOpen();
}
}
void FSequencer::ToggleSidebarDrawerDocked()
{
if (SequencerWidget.IsValid())
{
SequencerWidget->ToggleSidebarDrawerDock();
}
}
FText FSequencer::GetSidebarSelectionDrawerToolTipText() const
{
const TSharedRef<const FInputChord> DrawerOpenActiveChord = FSequencerCommands::Get().ToggleSidebarSelectionDrawerOpen->GetFirstValidChord();
const TSharedRef<const FInputChord> DrawerDockActiveChord = FSequencerCommands::Get().ToggleSidebarDrawerDock->GetFirstValidChord();
FText ToolTipText = LOCTEXT("SelectionDetailsPanelTooltip", "Open Sequencer selection details panel.");
if (DrawerOpenActiveChord->IsValidChord() || DrawerDockActiveChord->IsValidChord())
{
ToolTipText = FText::Format(LOCTEXT("ExtendedSelectionDetailsPanelTooltip", "{0}\n"), ToolTipText);
}
if (DrawerOpenActiveChord->IsValidChord())
{
ToolTipText = FText::Format(LOCTEXT("ExtendedSelectionDrawerOpenDetailsPanelTooltip", "{0}\n"
"{1} to toggle the drawer open or closed")
, ToolTipText
, DrawerOpenActiveChord->GetInputText(true));
}
if (DrawerDockActiveChord->IsValidChord())
{
ToolTipText = FText::Format(LOCTEXT("ExtendedSelectionDrawerDockDetailsPanelTooltip", "{0}\n"
"{1} to toggle the drawer docked or undocked")
, ToolTipText
, DrawerDockActiveChord->GetInputText(true));
}
return ToolTipText;
}
TSharedRef<ISequencerTrackFilters> FSequencer::GetFilterInterface() const
{
return FilterBar.ToSharedRef();
}
TSharedRef<FSequencerFilterBar> FSequencer::GetFilterBar() const
{
return FilterBar.ToSharedRef();
}
EVisibility FSequencer::GetPlayRateComboVisibility() const
{
using namespace UE::Sequencer;
TViewModelPtr<IClockExtension> ClockExtension = GetViewModel()->GetRootSequenceModel().ImplicitCast();
if (!ClockExtension || ClockExtension->ShouldShowPlayRateCombo(AsShared()))
{
return EVisibility::Visible;
}
return EVisibility::Collapsed;
}
void FSequencer::SortSelectedOutlinerItems(const bool bInSortBySelectOrder, const bool bInDescending)
{
using namespace UE::Sequencer;
const TSharedPtr<FSequencerSelection> SequencerSelection = ViewModel->GetSelection();
if (!SequencerSelection.IsValid())
{
return;
}
TArray<TViewModelPtr<IOutlinerExtension>> OutlinerItems;
for (const TViewModelPtr<IOutlinerExtension> Item : SequencerSelection->Outliner)
{
OutlinerItems.Add(Item);
}
SequencerHelpers::SortOutlinerItems(*this, OutlinerItems, bInSortBySelectOrder, bInDescending);
}
bool FSequencer::CanAlignSelectionToPlayhead() const
{
return FSequencerSelectionAlignmentUtils::CanAlignSelection(*this);
}
void FSequencer::AlignSelectionToPlayhead() const
{
FSequencerSelectionAlignmentUtils::AlignSelectionToPlayhead(*this);
}
void FSequencer::ToggleTrackSelectionPin()
{
using namespace UE::Sequencer;
FOutlinerSelection& OutlinerSelection = GetSelection().Outliner;
const bool bAllSame = Algo::AllOf(OutlinerSelection, [](const FViewModelPtr& InSelection)
{
for (const TViewModelPtr<IPinnableExtension>& Extension : InSelection->GetDescendantsOfType<IPinnableExtension>(true))
{
if (Extension.IsValid() && Extension->IsPinned())
{
return true;
}
}
return false;
});
const bool bNewState = !bAllSame;
bool bAnyChanged = false;
for (const TViewModelPtr<IOutlinerExtension>& OutlinerItem : OutlinerSelection)
{
for (const TViewModelPtr<IPinnableExtension>& Extension : OutlinerItem.AsModel()->GetDescendantsOfType<IPinnableExtension>(true))
{
if (Extension->IsPinned() != bNewState)
{
Extension->SetPinned(bNewState);
NodeTree->SavePinnedState(*Extension.AsModel(), bNewState);
bAnyChanged = true;
}
}
}
if (bAnyChanged)
{
RefreshTree();
}
}
void FSequencer::ToggleTrackSelectionLock()
{
using namespace UE::Sequencer;
FOutlinerSelection& OutlinerSelection = GetSelection().Outliner;
const bool bAllSame = Algo::AllOf(OutlinerSelection, [](const FViewModelPtr& InSelection)
{
for (const TViewModelPtr<ILockableExtension>& Extension : InSelection->GetDescendantsOfType<ILockableExtension>(true))
{
if (Extension.IsValid() && Extension->GetLockState() != ELockableLockState::None)
{
return true;
}
}
return false;
});
const bool bNewState = !bAllSame;
bool bAnyChanged = false;
for (const TViewModelPtr<IOutlinerExtension>& OutlinerItem : OutlinerSelection)
{
for (const TViewModelPtr<ILockableExtension>& Extension : OutlinerItem.AsModel()->GetDescendantsOfType<ILockableExtension>(true))
{
const bool bThisState = Extension->GetLockState() == ELockableLockState::Locked;
if (bThisState != bNewState)
{
Extension->SetIsLocked(bNewState);
bAnyChanged = true;
}
}
}
if (bAnyChanged)
{
RefreshTree();
}
}
void FSequencer::ToggleTrackSelectionDeactive()
{
using namespace UE::Sequencer;
FOutlinerSelection& OutlinerSelection = GetSelection().Outliner;
const bool bAllSame = Algo::AllOf(OutlinerSelection, [](const FViewModelPtr& InSelection)
{
for (const TViewModelPtr<IDeactivatableExtension>& Extension : InSelection->GetDescendantsOfType<IDeactivatableExtension>(true))
{
if (Extension.IsValid() && Extension->IsDeactivated())
{
return true;
}
}
return false;
});
const bool bNewState = !bAllSame;
bool bAnyChanged = false;
for (const TViewModelPtr<IOutlinerExtension>& OutlinerItem : OutlinerSelection)
{
for (const TViewModelPtr<IDeactivatableExtension>& Extension : OutlinerItem.AsModel()->GetDescendantsOfType<IDeactivatableExtension>(true))
{
if (Extension->IsDeactivated() != bNewState)
{
Extension->SetIsDeactivated(bNewState);
bAnyChanged = true;
}
}
}
if (bAnyChanged)
{
RefreshTree();
}
}
void FSequencer::ToggleTrackSelectionMute()
{
using namespace UE::Sequencer;
FOutlinerSelection& OutlinerSelection = GetSelection().Outliner;
const bool bAllSame = Algo::AllOf(OutlinerSelection, [](const FViewModelPtr& InSelection)
{
for (const TViewModelPtr<IMutableExtension>& Extension : InSelection->GetDescendantsOfType<IMutableExtension>(true))
{
if (Extension.IsValid() && Extension->IsMuted())
{
return true;
}
}
return false;
});
const bool bNewState = !bAllSame;
bool bAnyChanged = false;
for (const TViewModelPtr<IOutlinerExtension>& OutlinerItem : OutlinerSelection)
{
for (const TViewModelPtr<IMutableExtension>& Extension : OutlinerItem.AsModel()->GetDescendantsOfType<IMutableExtension>(true))
{
if (Extension->IsMuted() != bNewState)
{
Extension->SetIsMuted(bNewState);
bAnyChanged = true;
}
}
}
if (bAnyChanged)
{
RefreshTree();
}
}
void FSequencer::ToggleTrackSelectionSolo()
{
using namespace UE::Sequencer;
FOutlinerSelection& OutlinerSelection = GetSelection().Outliner;
const bool bAllSame = Algo::AllOf(OutlinerSelection, [](const FViewModelPtr& InSelection)
{
for (const TViewModelPtr<ISoloableExtension>& Extension : InSelection->GetDescendantsOfType<ISoloableExtension>(true))
{
if (Extension.IsValid() && Extension->IsSolo())
{
return true;
}
}
return false;
});
const bool bNewState = !bAllSame;
bool bAnyChanged = false;
for (const TViewModelPtr<IOutlinerExtension>& OutlinerItem : OutlinerSelection)
{
for (const TViewModelPtr<ISoloableExtension>& Extension : OutlinerItem.AsModel()->GetDescendantsOfType<ISoloableExtension>(true))
{
if (Extension->IsSolo() != bNewState)
{
Extension->SetIsSoloed(bNewState);
bAnyChanged = true;
}
}
}
if (bAnyChanged)
{
RefreshTree();
}
}
TSharedPtr<UE::Sequencer::SOutlinerView> FSequencer::GetOutlinerViewWidget() const
{
return SequencerWidget->GetTreeView();
}
#undef LOCTEXT_NAMESPACE