// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerUtilities.h" #include "Misc/Paths.h" #include "Layout/Margin.h" #include "Fonts/SlateFontInfo.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SButton.h" #include "Styling/CoreStyle.h" #include "EditorStyleSet.h" #include "SequencerTrackNode.h" #include "MovieSceneTrack.h" #include "MovieSceneSection.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "ISequencerTrackEditor.h" #include "ISequencer.h" #include "Sequencer.h" #include "SequencerNodeTree.h" #include "DisplayNodes/SequencerObjectBindingNode.h" #include "ScopedTransaction.h" #include "UObject/Package.h" #include "AssetRegistryModule.h" #include "FileHelpers.h" #include "LevelSequence.h" #define LOCTEXT_NAMESPACE "FSequencerUtilities" static EVisibility GetRolloverVisibility(TAttribute HoverState, TWeakPtr WeakComboButton) { TSharedPtr ComboButton = WeakComboButton.Pin(); if (HoverState.Get() || ComboButton->IsOpen()) { return EVisibility::SelfHitTestInvisible; } else { return EVisibility::Collapsed; } } TSharedRef FSequencerUtilities::MakeAddButton(FText HoverText, FOnGetContent MenuContent, const TAttribute& HoverState, TWeakPtr InSequencer) { FSlateFontInfo SmallLayoutFont = FCoreStyle::GetDefaultFontStyle("Regular", 8); TSharedRef ComboButtonText = SNew(STextBlock) .Text(HoverText) .Font(SmallLayoutFont) .ColorAndOpacity( FSlateColor::UseForeground() ); TSharedRef ComboButton = SNew(SComboButton) .HasDownArrow(false) .IsFocusable(true) .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly") .ForegroundColor( FSlateColor::UseForeground() ) .IsEnabled_Lambda([=]() { return InSequencer.IsValid() ? !InSequencer.Pin()->IsReadOnly() : false; }) .OnGetMenuContent(MenuContent) .ContentPadding(FMargin(5, 2)) .HAlign(HAlign_Center) .VAlign(VAlign_Center) .ButtonContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(0,0,2,0)) [ SNew(SImage) .ColorAndOpacity( FSlateColor::UseForeground() ) .Image(FEditorStyle::GetBrush("Plus")) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ ComboButtonText ] ]; TAttribute Visibility = TAttribute::Create(TAttribute::FGetter::CreateStatic(GetRolloverVisibility, HoverState, TWeakPtr(ComboButton))); ComboButtonText->SetVisibility(Visibility); return ComboButton; } void FSequencerUtilities::PopulateMenu_CreateNewSection(FMenuBuilder& MenuBuilder, int32 RowIndex, UMovieSceneTrack* Track, TWeakPtr InSequencer) { if (!Track) { return; } auto CreateNewSection = [Track, InSequencer, RowIndex](EMovieSceneBlendType BlendType) { TSharedPtr Sequencer = InSequencer.Pin(); if (!Sequencer.IsValid()) { return; } FQualifiedFrameTime CurrentTime = Sequencer->GetLocalTime(); TRange VisibleRange = Sequencer->GetViewRange(); FScopedTransaction Transaction(LOCTEXT("AddSectionTransactionText", "Add Section")); if (UMovieSceneSection* NewSection = Track->CreateNewSection()) { int32 OverlapPriority = 0; for (UMovieSceneSection* Section : Track->GetAllSections()) { OverlapPriority = FMath::Max(Section->GetOverlapPriority() + 1, OverlapPriority); // Move existing sections on the same row or beyond so that they don't overlap with the new section if (Section != NewSection && Section->GetRowIndex() >= RowIndex) { Section->SetRowIndex(Section->GetRowIndex() + 1); } } Track->Modify(); int32 DurationFrames = ( (VisibleRange.Size() * 0.75) * CurrentTime.Rate ).FloorToFrame().Value; NewSection->SetRange(TRange(CurrentTime.Time.FrameNumber, CurrentTime.Time.FrameNumber + DurationFrames)); NewSection->SetOverlapPriority(OverlapPriority); NewSection->SetRowIndex(RowIndex); NewSection->SetBlendType(BlendType); Track->AddSection(*NewSection); Track->UpdateEasing(); Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::MovieSceneStructureItemAdded); Sequencer->EmptySelection(); Sequencer->SelectSection(NewSection); Sequencer->ThrobSectionSelection(); } else { Transaction.Cancel(); } }; FText NameOverride = Track->GetSupportedBlendTypes().Num() == 1 ? LOCTEXT("AddSectionText", "Add New Section") : FText(); FText TooltipOverride = Track->GetSupportedBlendTypes().Num() == 1 ? LOCTEXT("AddSectionToolTip", "Adds a new section at the current time") : FText(); const UEnum* MovieSceneBlendType = FindObjectChecked(ANY_PACKAGE, TEXT("EMovieSceneBlendType")); for (EMovieSceneBlendType BlendType : Track->GetSupportedBlendTypes()) { FText DisplayName = MovieSceneBlendType->GetDisplayNameTextByValue((int64)BlendType); FName EnumValueName = MovieSceneBlendType->GetNameByValue((int64)BlendType); MenuBuilder.AddMenuEntry( NameOverride.IsEmpty() ? DisplayName : NameOverride, TooltipOverride.IsEmpty() ? FText::Format(LOCTEXT("AddSectionFormatToolTip", "Adds a new {0} section at the current time"), DisplayName) : TooltipOverride, FSlateIcon("EditorStyle", EnumValueName), FUIAction(FExecuteAction::CreateLambda(CreateNewSection, BlendType)) ); } } void FSequencerUtilities::PopulateMenu_SetBlendType(FMenuBuilder& MenuBuilder, UMovieSceneSection* Section, TWeakPtr InSequencer) { PopulateMenu_SetBlendType(MenuBuilder, TArray>({ Section }), InSequencer); } void FSequencerUtilities::PopulateMenu_SetBlendType(FMenuBuilder& MenuBuilder, const TArray>& InSections, TWeakPtr InSequencer) { auto Execute = [InSections, InSequencer](EMovieSceneBlendType BlendType) { FScopedTransaction Transaction(LOCTEXT("SetBlendType", "Set Blend Type")); for (TWeakObjectPtr WeakSection : InSections) { if (UMovieSceneSection* Section = WeakSection.Get()) { Section->Modify(); Section->SetBlendType(BlendType); } } TSharedPtr Sequencer = StaticCastSharedPtr(InSequencer.Pin()); if (Sequencer.IsValid()) { // If the blend type is changed to additive or relative, restore the state of the objects boud to this section before evaluating again. // This allows the additive or relative to evaluate based on the initial values of the object, rather than the current animated values. if (BlendType == EMovieSceneBlendType::Additive || BlendType == EMovieSceneBlendType::Relative) { TSet ObjectsToRestore; TSharedRef SequencerNodeTree = Sequencer->GetNodeTree(); for (TWeakObjectPtr WeakSection : InSections) { if (UMovieSceneSection* Section = WeakSection.Get()) { TOptional SectionHandle = SequencerNodeTree->GetSectionHandle(Section); if (!SectionHandle) { continue; } TSharedPtr ParentObjectBindingNode = SectionHandle->GetTrackNode()->FindParentObjectBindingNode(); if (!ParentObjectBindingNode.IsValid()) { continue; } for (TWeakObjectPtr<> BoundObject : Sequencer->FindObjectsInCurrentSequence(ParentObjectBindingNode->GetObjectBinding())) { if (AActor* BoundActor = Cast(BoundObject)) { for (UActorComponent* Component : TInlineComponentArray(BoundActor)) { if (Component) { ObjectsToRestore.Add(Component); } } } ObjectsToRestore.Add(BoundObject.Get()); } } } for (UObject* ObjectToRestore : ObjectsToRestore) { Sequencer->PreAnimatedState.RestorePreAnimatedState(*ObjectToRestore); } } Sequencer->NotifyMovieSceneDataChanged(EMovieSceneDataChangeType::TrackValueChanged); } }; const UEnum* MovieSceneBlendType = FindObjectChecked(ANY_PACKAGE, TEXT("EMovieSceneBlendType")); for (int32 NameIndex = 0; NameIndex < MovieSceneBlendType->NumEnums() - 1; ++NameIndex) { EMovieSceneBlendType BlendType = (EMovieSceneBlendType)MovieSceneBlendType->GetValueByIndex(NameIndex); // Include this if any section supports it bool bAnySupported = false; for (TWeakObjectPtr WeakSection : InSections) { UMovieSceneSection* Section = WeakSection.Get(); if (Section && Section->GetSupportedBlendTypes().Contains(BlendType)) { bAnySupported = true; break; } } if (!bAnySupported) { continue; } FName EnumValueName = MovieSceneBlendType->GetNameByIndex(NameIndex); MenuBuilder.AddMenuEntry( MovieSceneBlendType->GetDisplayNameTextByIndex(NameIndex), MovieSceneBlendType->GetToolTipTextByIndex(NameIndex), FSlateIcon("EditorStyle", EnumValueName), FUIAction(FExecuteAction::CreateLambda(Execute, BlendType)) ); } } FName FSequencerUtilities::GetUniqueName( FName CandidateName, const TArray& ExistingNames ) { if (!ExistingNames.Contains(CandidateName)) { return CandidateName; } FString CandidateNameString = CandidateName.ToString(); FString BaseNameString = CandidateNameString; if ( CandidateNameString.Len() >= 3 && CandidateNameString.Right(3).IsNumeric() ) { BaseNameString = CandidateNameString.Left( CandidateNameString.Len() - 3 ); } FName UniqueName = FName(*BaseNameString); int32 NameIndex = 1; while ( ExistingNames.Contains( UniqueName ) ) { UniqueName = FName( *FString::Printf(TEXT("%s%i"), *BaseNameString, NameIndex ) ); NameIndex++; } return UniqueName; } TArray FSequencerUtilities::GetAssociatedMapPackages(const ULevelSequence* InSequence) { if (!InSequence) { return TArray(); } FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); const FName LSMapPathName = *InSequence->GetOutermost()->GetPathName(); TArray AssociatedMaps; TArray AssociatedAssets; // This makes the assumption these functions will append the array, and not clear it. AssetRegistryModule.Get().GetReferencers(LSMapPathName, AssociatedAssets); AssetRegistryModule.Get().GetDependencies(LSMapPathName, AssociatedAssets); for (FAssetIdentifier& AssociatedMap : AssociatedAssets) { FString MapFilePath; FString LevelPath = AssociatedMap.PackageName.ToString(); if (FEditorFileUtils::IsMapPackageAsset(LevelPath, MapFilePath)) { AssociatedMaps.AddUnique(LevelPath); } } AssociatedMaps.Sort([](const FString& One, const FString& Two) { return FPaths::GetBaseFilename(One) < FPaths::GetBaseFilename(Two); }); return AssociatedMaps; } #undef LOCTEXT_NAMESPACE