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

582 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_GetSequenceBinding.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "KismetCompiler.h"
#include "BlueprintNodeSpawner.h"
#include "EditorCategoryUtils.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "Framework/Application/SlateApplication.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "PropertyCustomizationHelpers.h"
#include "MovieSceneSequence.h"
#include "ToolMenus.h"
#include "MovieSceneObjectBindingIDPicker.h"
#include "SGraphNode.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "GraphEditorSettings.h"
#include "Engine/Selection.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SComboBox.h"
#include "Editor.h"
#include "ScopedTransaction.h"
static const FName OutputPinName(TEXT("Output"));
static const FName SequencePinName(TEXT("Sequence"));
#define LOCTEXT_NAMESPACE "UK2Node_GetSequenceBinding"
void EnsureFullyPreloaded(UObject* Object)
{
if (!Object)
{
return;
}
FLinkerLoad* Linker = Object->GetLinker();
if (Object->HasAnyFlags(RF_NeedLoad))
{
if (ensure(Linker))
{
Linker->Preload(Object);
check(!Object->HasAnyFlags(RF_NeedLoad));
}
}
// We only want to ensure that _loaded_ objects have RF_LoadCompleted set.
// Some objects can be created during postload, so we don't need to verify RF_LoadCompleted in those cases.
if (Linker)
{
check(Object->HasAnyFlags(RF_LoadCompleted));
}
TArray<UObject*> ObjectReferences;
FReferenceFinder(ObjectReferences, nullptr, false, true, false, true).FindReferences(Object);
for (UObject* Reference : ObjectReferences)
{
check(Reference);
const bool bIsMovieSceneType =
Reference->IsA<UMovieSceneSequence>() ||
Reference->IsA<UMovieScene>() ||
Reference->IsA<UMovieSceneTrack>() ||
Reference->IsA<UMovieSceneSection>()
;
if (bIsMovieSceneType)
{
EnsureFullyPreloaded(Reference);
}
}
}
class FKCHandler_GetSequenceBinding : public FNodeHandlingFunctor
{
public:
FKCHandler_GetSequenceBinding(FKismetCompilerContext& InCompilerContext)
: FNodeHandlingFunctor(InCompilerContext)
{
}
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
UK2Node_GetSequenceBinding* BindingNode = CastChecked<UK2Node_GetSequenceBinding>(Node);
for (UEdGraphPin* Pin : BindingNode->GetAllPins())
{
if (Pin->Direction == EGPD_Output && Pin->LinkedTo.Num())
{
FBPTerminal* Term = RegisterLiteral(Context, Pin);
// Intentionally pass BindingPtr to both the data and defaults for ExportText to ensure that we always disable delta-serialization on the struct
FMovieSceneObjectBindingID* BindingPtr = &BindingNode->Binding;
FMovieSceneObjectBindingID::StaticStruct()->ExportText(Term->Name, BindingPtr, BindingPtr, nullptr, 0, nullptr);
}
}
}
};
void UK2Node_GetSequenceBinding::SetSequence(UMovieSceneSequence* InSequence)
{
SourceMovieSequence = InSequence;
}
void UK2Node_GetSequenceBinding::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
const bool bConvertSoftToHardReference =
Ar.IsLoading() &&
((Ar.GetPortFlags() & PPF_Duplicate) == 0) &&
!SourceSequence_DEPRECATED.IsNull()
;
if (bConvertSoftToHardReference)
{
SourceMovieSequence = Cast<UMovieSceneSequence>(SourceSequence_DEPRECATED.TryLoad());
SourceSequence_DEPRECATED.Reset();
}
}
void UK2Node_GetSequenceBinding::ValidateNodeDuringCompilation(FCompilerResultsLog& MessageLog) const
{
Super::ValidateNodeDuringCompilation(MessageLog);
UMovieScene* MovieScene = GetObjectMovieScene();
if (!MovieScene)
{
const FText MessageText = LOCTEXT("InvalidSequenceBinding_NoSequence", "Invalid sequence binding specified on node @@ (could not find sequence).");
MessageLog.Warning(*MessageText.ToString(), this);
}
else if (!MovieScene->FindPossessable(Binding.GetGuid()) && !MovieScene->FindSpawnable(Binding.GetGuid()))
{
const FText MessageText = LOCTEXT("InvalidSequenceBinding_Unresolved", "Invalid sequence binding specified on node @@.");
MessageLog.Warning(*MessageText.ToString(), this);
}
}
void UK2Node_GetSequenceBinding::AllocateDefaultPins()
{
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UMovieSceneSequence::StaticClass(), SequencePinName);
// Result pin
UEdGraphPin* ResultPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Struct, FMovieSceneObjectBindingID::StaticStruct(), UEdGraphSchema_K2::PN_ReturnValue);
ResultPin->PinFriendlyName = LOCTEXT("SequenceBindingOutput", "Binding");
Super::AllocateDefaultPins();
}
void UK2Node_GetSequenceBinding::PostPlacedNewNode()
{
// Attempt to assign the sequence asset from our outer if this BP is contained within a sequence
if (UMovieSceneSequence* OuterSequence = GetTypedOuter<UMovieSceneSequence>())
{
SourceMovieSequence = OuterSequence;
}
Super::PostPlacedNewNode();
}
UMovieScene* UK2Node_GetSequenceBinding::GetObjectMovieScene() const
{
if (SourceMovieSequence && Binding.IsValid())
{
FMovieSceneSequenceID SequenceID = Binding.GetRelativeSequenceID();
if (SequenceID == MovieSceneSequenceID::Root)
{
// Look it up in the moviescene itself
return SourceMovieSequence->GetMovieScene();
}
else
{
bool bHierarchyIsValid = true;
// Ensure the hierarchy is valid (ie, the user hasn't changed a sub sequence for something else)
FMovieSceneSequenceID CurrentSequenceID = SequenceID;
while (CurrentSequenceID != MovieSceneSequenceID::Root)
{
const FMovieSceneSubSequenceData* SubData = SequenceHierarchyCache.FindSubData(CurrentSequenceID);
const UMovieSceneSequence* SubSequence = SubData ? SubData->GetSequence() : nullptr;
if (!SubSequence || SubSequence->GetSignature() != SequenceSignatureCache.FindRef(CurrentSequenceID))
{
bHierarchyIsValid = false;
break;
}
const FMovieSceneSequenceHierarchyNode* Node = SequenceHierarchyCache.FindNode(CurrentSequenceID);
if (!Node)
{
bHierarchyIsValid = false;
break;
}
CurrentSequenceID = Node->ParentID;
}
// If it's not valid, it needs recompiling
if (!bHierarchyIsValid)
{
// Recompile the hierarchy
SequenceSignatureCache.Reset();
SequenceHierarchyCache = FMovieSceneSequenceHierarchy();
UMovieSceneCompiledDataManager::CompileHierarchy(SourceMovieSequence, &SequenceHierarchyCache, EMovieSceneServerClientMask::All);
for (const TTuple<FMovieSceneSequenceID, FMovieSceneSubSequenceData>& Pair : SequenceHierarchyCache.AllSubSequenceData())
{
const UMovieSceneSequence* SubSequence = Pair.Value.GetSequence();
if (ensure(SubSequence))
{
SequenceSignatureCache.Add(Pair.Key, SubSequence->GetSignature());
}
}
}
UMovieScene* MovieScene = nullptr;
if (const FMovieSceneSubSequenceData* SubData = SequenceHierarchyCache.FindSubData(SequenceID))
{
UMovieSceneSequence* SubSequence = SubData->GetSequence();
return SubSequence ? SubSequence->GetMovieScene() : nullptr;
}
}
}
return nullptr;
}
FNodeHandlingFunctor* UK2Node_GetSequenceBinding::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_GetSequenceBinding(CompilerContext);
}
void UK2Node_GetSequenceBinding::PreloadRequiredAssets()
{
EnsureFullyPreloaded(SourceMovieSequence);
}
FText UK2Node_GetSequenceBinding::GetSequenceName() const
{
return SourceMovieSequence ? FText::FromName(SourceMovieSequence->GetFName()) : LOCTEXT("NoSequence", "No Sequence");
}
FText UK2Node_GetSequenceBinding::GetBindingName() const
{
UMovieScene* MovieScene = GetObjectMovieScene();
return MovieScene ? MovieScene->GetObjectDisplayName(Binding.GetGuid()) : FText();
}
FText UK2Node_GetSequenceBinding::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
FText BindingName = GetBindingName();
return BindingName.IsEmpty() ? LOCTEXT("NodeTitle", "Get Sequence Binding") : FText::Format(LOCTEXT("NodeTitle_Format", "Get Sequence Binding ({0})"), BindingName);
}
FText UK2Node_GetSequenceBinding::GetTooltipText() const
{
return LOCTEXT("NodeTooltip", "Access an identifier for any object binding within a sequence");
}
FText UK2Node_GetSequenceBinding::GetMenuCategory() const
{
return LOCTEXT("NodeCategory", "Sequencer|Player|Bindings");
}
FSlateIcon UK2Node_GetSequenceBinding::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.GetSequenceBinding");
return Icon;
}
void UK2Node_GetSequenceBinding::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
Super::GetNodeContextMenuActions(Menu, Context);
if (!Context->bIsDebugging)
{
FToolMenuSection& Section = Menu->AddSection("K2NodeGetSequenceBinding", LOCTEXT("ThisNodeHeader", "This Node"));
if (!Context->Pin)
{
Section.AddSubMenu(
"SetSequence",
LOCTEXT("SetSequence_Text", "Sequence"),
LOCTEXT("SetSequence_ToolTip", "Sets the sequence to get a binding from"),
FNewToolMenuDelegate::CreateLambda([this](UToolMenu* SubMenu)
{
TArray<const UClass*> AllowedClasses({ UMovieSceneSequence::StaticClass() });
TSharedRef<SWidget> MenuContent = PropertyCustomizationHelpers::MakeAssetPickerWithMenu(
FAssetData(SourceMovieSequence),
true /* bAllowClear */,
AllowedClasses,
PropertyCustomizationHelpers::GetNewAssetFactoriesForClasses(AllowedClasses),
FOnShouldFilterAsset(),
FOnAssetSelected::CreateUObject(const_cast<UK2Node_GetSequenceBinding*>(this), &UK2Node_GetSequenceBinding::SetSequence),
FSimpleDelegate());
SubMenu->AddMenuEntry("Section", FToolMenuEntry::InitWidget("Widget", MenuContent, FText::GetEmpty(), false));
}));
}
}
}
void UK2Node_GetSequenceBinding::SetSequence(const FAssetData& InAssetData)
{
FSlateApplication::Get().DismissAllMenus();
const FScopedTransaction Transaction(LOCTEXT("SetSequence", "Set Sequence"));
Modify();
SourceMovieSequence = Cast<UMovieSceneSequence>(InAssetData.GetAsset());
}
void UK2Node_GetSequenceBinding::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
UClass* ActionKey = GetClass();
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
check(NodeSpawner != nullptr);
ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
}
}
TSharedPtr<SGraphNode> UK2Node_GetSequenceBinding::CreateVisualWidget()
{
class SGraphNodeGetSequenceBinding : public SGraphNode, FMovieSceneObjectBindingIDPicker
{
public:
SLATE_BEGIN_ARGS(SGraphNodeGetSequenceBinding){}
SLATE_END_ARGS()
SGraphNodeGetSequenceBinding()
: FMovieSceneObjectBindingIDPicker(MovieSceneSequenceID::Root, nullptr)
{}
void Construct(const FArguments& InArgs, UK2Node_GetSequenceBinding* InNode)
{
bNeedsUpdate = false;
GraphNode = InNode;
Initialize();
UpdateGraphNode();
}
virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
UK2Node_GetSequenceBinding* Node = CastChecked<UK2Node_GetSequenceBinding>(GraphNode);
if (bNeedsUpdate || Node->SourceMovieSequence != LastSequence.Get())
{
Initialize();
UpdateGraphNode();
bNeedsUpdate = false;
}
LastSequence = Node->SourceMovieSequence;
SGraphNode::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
}
virtual void CreateStandardPinWidget(UEdGraphPin* Pin) override
{
if (Pin->PinName == SequencePinName)
{
CreateDetailsPickers();
}
else
{
SGraphNode::CreateStandardPinWidget(Pin);
}
}
void OnAssetSelectedFromPicker(const FAssetData& AssetData)
{
CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->SetSequence(AssetData);
Initialize();
UpdateGraphNode();
}
FText GetAssetName() const
{
return CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->GetSequenceName();
}
TSharedRef<SWidget> GenerateAssetPicker()
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
FAssetPickerConfig AssetPickerConfig;
AssetPickerConfig.Filter.ClassPaths.Add(UMovieSceneSequence::StaticClass()->GetClassPathName());
AssetPickerConfig.bAllowNullSelection = true;
AssetPickerConfig.Filter.bRecursiveClasses = true;
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SGraphNodeGetSequenceBinding::OnAssetSelectedFromPicker);
AssetPickerConfig.InitialAssetViewType = EAssetViewType::List;
AssetPickerConfig.bAllowDragging = false;
return SNew(SBox)
.HeightOverride(300)
.WidthOverride(300)
[
SNew(SBorder)
.BorderImage( FAppStyle::GetBrush("Menu.Background") )
[
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)
]
];
}
FReply UseSelectedAsset()
{
UMovieSceneSequence* Sequence = Cast<UMovieSceneSequence>(GEditor->GetSelectedObjects()->GetTop(UMovieSceneSequence::StaticClass()));
if (Sequence)
{
CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->SetSequence(Sequence);
Initialize();
UpdateGraphNode();
}
return FReply::Handled();
}
FReply BrowseToAsset()
{
UMovieSceneSequence* Sequence = CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->SourceMovieSequence;
if (Sequence)
{
TArray<UObject*> Objects{ Sequence };
GEditor->SyncBrowserToObjects(Objects);
}
return FReply::Handled();
}
void CreateDetailsPickers()
{
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(Settings->GetInputPinPadding())
[
SNew(SHorizontalBox)
// Asset Combo
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2,0)
.MaxWidth(200.0f)
[
SNew(SComboButton)
.ButtonStyle( FAppStyle::Get(), "PropertyEditor.AssetComboStyle" )
.ForegroundColor(this, &SGraphNodeGetSequenceBinding::OnGetComboForeground)
.ButtonColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetWidgetBackground)
.ContentPadding(FMargin(2,2,2,1))
.MenuPlacement(MenuPlacement_BelowAnchor)
.ButtonContent()
[
SNew(STextBlock)
.ColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetComboForeground)
.TextStyle( FAppStyle::Get(), "PropertyEditor.AssetClass" )
.Font( FAppStyle::GetFontStyle( "PropertyWindow.NormalFont" ) )
.Text( this, &SGraphNodeGetSequenceBinding::GetAssetName )
]
.OnGetMenuContent(this, &SGraphNodeGetSequenceBinding::GenerateAssetPicker)
]
// Use button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(1,0)
.VAlign(VAlign_Center)
[
SNew(SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.OnClicked(this, &SGraphNodeGetSequenceBinding::UseSelectedAsset)
.ButtonColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetWidgetBackground)
.ContentPadding(1.f)
.ToolTipText(LOCTEXT("GraphNodeGetSequenceBinding_Use_Tooltip", "Use asset browser selection"))
[
SNew(SImage)
.ColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetWidgetForeground)
.Image( FAppStyle::GetBrush(TEXT("Icons.CircleArrowLeft")) )
]
]
// Browse button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(1,0)
.VAlign(VAlign_Center)
[
SNew(SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.OnClicked(this, &SGraphNodeGetSequenceBinding::BrowseToAsset)
.ButtonColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetWidgetBackground)
.ContentPadding(0)
.ToolTipText(LOCTEXT("GraphNodeGetSequenceBinding_Browse_Tooltip", "Browse"))
[
SNew(SImage)
.ColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetWidgetForeground)
.Image( FAppStyle::GetBrush(TEXT("Icons.Search")) )
]
]
];
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(Settings->GetInputPinPadding())
[
SNew(SBox)
.MaxDesiredWidth(200.0f)
.Padding(FMargin(2,0))
[
SNew(SComboButton)
.ButtonStyle( FAppStyle::Get(), "PropertyEditor.AssetComboStyle" )
.ToolTipText(this, &SGraphNodeGetSequenceBinding::GetToolTipText)
.ForegroundColor(this, &SGraphNodeGetSequenceBinding::OnGetComboForeground)
.ButtonColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetWidgetBackground)
.ContentPadding(FMargin(2,2,2,1))
.MenuPlacement(MenuPlacement_BelowAnchor)
.ButtonContent()
[
GetCurrentItemWidget(
SNew(STextBlock)
.TextStyle( FAppStyle::Get(), "PropertyEditor.AssetClass" )
.Font( FAppStyle::GetFontStyle( "PropertyWindow.NormalFont" ) )
.ColorAndOpacity(this, &SGraphNodeGetSequenceBinding::OnGetComboForeground)
)
]
.OnGetMenuContent(this, &SGraphNodeGetSequenceBinding::GetPickerMenu)
]
];
}
private:
virtual void SetCurrentValue(const FMovieSceneObjectBindingID& InBindingId) override
{
const FScopedTransaction Transaction(LOCTEXT("SetBindng", "Set Binding"));
GraphNode->Modify();
CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->Binding = InBindingId;
bNeedsUpdate = true;
}
virtual FMovieSceneObjectBindingID GetCurrentValue() const override
{
return CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->Binding;
}
virtual UMovieSceneSequence* GetSequence() const override
{
return CastChecked<UK2Node_GetSequenceBinding>(GraphNode)->SourceMovieSequence;
}
FSlateColor OnGetComboForeground() const
{
return FSlateColor( FLinearColor( 1.f, 1.f, 1.f, IsHovered() ? 1.f : 0.6f ) );
}
FSlateColor OnGetWidgetForeground() const
{
return FSlateColor( FLinearColor( 1.f, 1.f, 1.f, IsHovered() ? 1.f : 0.15f ) );
}
FSlateColor OnGetWidgetBackground() const
{
return FSlateColor( FLinearColor( 1.f, 1.f, 1.f, IsHovered() ? 0.8f : 0.4f ) );
}
private:
TWeakObjectPtr<UMovieSceneSequence> LastSequence;
bool bNeedsUpdate;
};
return SNew(SGraphNodeGetSequenceBinding, this);
}
#undef LOCTEXT_NAMESPACE