Files
UnrealEngineUWP/Engine/Source/Editor/AnimationBlueprintEditor/Private/AnimationNodes/SAnimationGraphNode.cpp
Lauren Barnes 6248f8d412 Replacing legacy EditorStyle calls with AppStyle
#preflight 6272a74d2f6d177be3c6fdda
#rb Matt.Kuhlenschmidt

#ROBOMERGE-OWNER: Lauren.Barnes
#ROBOMERGE-AUTHOR: lauren.barnes
#ROBOMERGE-SOURCE: CL 20057269 via CL 20070159 via CL 20072035 via CL 20072203
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v943-19904690)
#ROBOMERGE-CONFLICT from-shelf

[CL 20105363 by Lauren Barnes in ue5-main branch]
2022-05-09 13:12:28 -04:00

448 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimationNodes/SAnimationGraphNode.h"
#include "Widgets/SBoxPanel.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "AnimGraphNode_Base.h"
#include "IDocumentation.h"
#include "AnimationEditorUtils.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Animation/AnimInstance.h"
#include "GraphEditorSettings.h"
#include "SLevelOfDetailBranchNode.h"
#include "Widgets/Layout/SSpacer.h"
#include "AnimationGraphSchema.h"
#include "AnimGraphNode_CustomProperty.h"
#include "BlueprintMemberReferenceCustomization.h"
#include "SGraphPin.h"
#include "Widgets/Layout/SWrapBox.h"
#include "Brushes/SlateColorBrush.h"
#include "PropertyEditorModule.h"
#include "IPropertyRowGenerator.h"
#include "IDetailTreeNode.h"
#include "Widgets/Layout/SGridPanel.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "SGraphPanel.h"
#include "SPoseWatchOverlay.h"
#define LOCTEXT_NAMESPACE "AnimationGraphNode"
void SAnimationGraphNode::Construct(const FArguments& InArgs, UAnimGraphNode_Base* InNode)
{
this->GraphNode = InNode;
this->SetCursor(EMouseCursor::CardinalCross);
this->UpdateGraphNode();
ReconfigurePinWidgetsForPropertyBindings(CastChecked<UAnimGraphNode_Base>(GraphNode), SharedThis(this), [this](UEdGraphPin* InPin){ return FindWidgetForPin(InPin); });
const FSlateBrush* ImageBrush = FAppStyle::Get().GetBrush(TEXT("Graph.AnimationFastPathIndicator"));
IndicatorWidget =
SNew(SImage)
.Image(ImageBrush)
.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("AnimGraphNodeIndicatorTooltip", "Fast path enabled: This node is not using any Blueprint calls to update its data."), NULL, TEXT("Shared/GraphNodes/Animation"), TEXT("GraphNode_FastPathInfo")))
.Visibility(EVisibility::Visible);
PoseViewWidget = SNew(SPoseWatchOverlay, InNode);
LastHighDetailSize = FVector2D::ZeroVector;
}
void SAnimationGraphNode::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SGraphNodeK2Base::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (CachedContentArea != nullptr && !UseLowDetailNodeContent())
{
LastHighDetailSize = CachedContentArea->GetTickSpaceGeometry().Size;
}
}
TArray<FOverlayWidgetInfo> SAnimationGraphNode::GetOverlayWidgets(bool bSelected, const FVector2D& WidgetSize) const
{
TArray<FOverlayWidgetInfo> Widgets;
if (UAnimGraphNode_Base* AnimNode = CastChecked<UAnimGraphNode_Base>(GraphNode, ECastCheckedType::NullAllowed))
{
if (AnimNode->BlueprintUsage == EBlueprintUsage::DoesNotUseBlueprint)
{
const FSlateBrush* ImageBrush = FAppStyle::Get().GetBrush(TEXT("Graph.AnimationFastPathIndicator"));
FOverlayWidgetInfo Info;
Info.OverlayOffset = FVector2D(WidgetSize.X - (ImageBrush->ImageSize.X * 0.5f), -(ImageBrush->ImageSize.Y * 0.5f));
Info.Widget = IndicatorWidget;
Widgets.Add(Info);
}
if (PoseViewWidget->IsPoseWatchValid())
{
FOverlayWidgetInfo Info;
Info.OverlayOffset = PoseViewWidget->GetOverlayOffset();
Info.Widget = PoseViewWidget;
Widgets.Add(Info);
}
}
return Widgets;
}
TSharedRef<SWidget> SAnimationGraphNode::CreateTitleWidget(TSharedPtr<SNodeTitle> InNodeTitle)
{
// Store title widget reference
NodeTitle = InNodeTitle;
// hook up invalidation delegate
UAnimGraphNode_Base* AnimGraphNode = CastChecked<UAnimGraphNode_Base>(GraphNode);
AnimGraphNode->OnNodeTitleChangedEvent().AddSP(this, &SAnimationGraphNode::HandleNodeTitleChanged);
return SGraphNodeK2Base::CreateTitleWidget(InNodeTitle);
}
void SAnimationGraphNode::HandleNodeTitleChanged()
{
if(NodeTitle.IsValid())
{
NodeTitle->MarkDirty();
}
}
void SAnimationGraphNode::GetNodeInfoPopups(FNodeInfoContext* Context, TArray<FGraphInformationPopupInfo>& Popups) const
{
SGraphNodeK2Base::GetNodeInfoPopups(Context, Popups);
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(GraphNode));
if(AnimBlueprint)
{
UAnimInstance* ActiveObject = Cast<UAnimInstance>(AnimBlueprint->GetObjectBeingDebugged());
UAnimBlueprintGeneratedClass* Class = AnimBlueprint->GetAnimBlueprintGeneratedClass();
const FLinearColor Color(1.f, 0.5f, 0.25f);
// Display various types of debug data
if ((ActiveObject != NULL) && (Class != NULL))
{
if (Class->GetAnimNodeProperties().Num())
{
if(int32* NodeIndexPtr = Class->GetAnimBlueprintDebugData().NodePropertyToIndexMap.Find(TWeakObjectPtr<UAnimGraphNode_Base>(Cast<UAnimGraphNode_Base>(GraphNode))))
{
int32 AnimNodeIndex = *NodeIndexPtr;
// reverse node index temporarily because of a bug in NodeGuidToIndexMap
AnimNodeIndex = Class->GetAnimNodeProperties().Num() - AnimNodeIndex - 1;
if (FAnimBlueprintDebugData::FNodeValue* DebugInfo = Class->GetAnimBlueprintDebugData().NodeValuesThisFrame.FindByPredicate([AnimNodeIndex](const FAnimBlueprintDebugData::FNodeValue& InValue){ return InValue.NodeID == AnimNodeIndex; }))
{
Popups.Emplace(nullptr, Color, DebugInfo->Text);
}
}
}
}
}
}
void SAnimationGraphNode::CreateBelowPinControls(TSharedPtr<SVerticalBox> MainBox)
{
if (UAnimGraphNode_Base* AnimNode = CastChecked<UAnimGraphNode_Base>(GraphNode, ECastCheckedType::NullAllowed))
{
auto UseLowDetailNode = [this]()
{
return GetCurrentLOD() <= EGraphRenderingLOD::LowDetail;
};
// Insert above the error reporting bar
MainBox->InsertSlot(FMath::Max(0, MainBox->NumSlots() - TagAndFunctionsSlotReverseIndex))
.AutoHeight()
.Padding(4.0f, 2.0f, 4.0f, 2.0f)
[
SNew(SVerticalBox)
.IsEnabled_Lambda([this](){ return IsNodeEditable(); })
+SVerticalBox::Slot()
.AutoHeight()
[
CreateNodeFunctionsWidget(AnimNode, MakeAttributeLambda(UseLowDetailNode))
]
];
MainBox->InsertSlot(FMath::Max(0, MainBox->NumSlots() - TagAndFunctionsSlotReverseIndex))
.AutoHeight()
.Padding(4.0f, 2.0f, 4.0f, 2.0f)
[
SNew(SVerticalBox)
.IsEnabled_Lambda([this](){ return IsNodeEditable(); })
+SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
[
CreateNodeTagWidget(AnimNode, MakeAttributeLambda(UseLowDetailNode))
]
];
}
}
TSharedRef<SWidget> SAnimationGraphNode::CreateNodeContentArea()
{
CachedContentArea = SGraphNodeK2Base::CreateNodeContentArea();
return SNew(SLevelOfDetailBranchNode)
.UseLowDetailSlot(this, &SAnimationGraphNode::UseLowDetailNodeContent)
.LowDetail()
[
SNew(SSpacer)
.Size(this, &SAnimationGraphNode::GetLowDetailDesiredSize)
]
.HighDetail()
[
CachedContentArea.ToSharedRef()
];
}
bool SAnimationGraphNode::UseLowDetailNodeContent() const
{
if (LastHighDetailSize.IsNearlyZero())
{
return false;
}
if (const SGraphPanel* MyOwnerPanel = GetOwnerPanel().Get())
{
return (MyOwnerPanel->GetCurrentLOD() <= EGraphRenderingLOD::LowestDetail);
}
return false;
}
FVector2D SAnimationGraphNode::GetLowDetailDesiredSize() const
{
return LastHighDetailSize;
}
// Widget used to allow functions to be viewed and edited on nodes
class SAnimNodeFunctionsWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SAnimNodeFunctionsWidget) {}
SLATE_ATTRIBUTE(bool, UseLowDetail)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UAnimGraphNode_Base* InNode)
{
UseLowDetail = InArgs._UseLowDetail;
FPropertyEditorModule& PropertyEditorModule = FModuleManager::Get().LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyRowGenerator = PropertyEditorModule.CreatePropertyRowGenerator(FPropertyRowGeneratorArgs());
PropertyRowGenerator->RegisterInstancedCustomPropertyTypeLayout(FMemberReference::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FBlueprintMemberReferenceDetails::MakeInstance));
PropertyRowGenerator->SetObjects({ InNode });
TSharedPtr<SGridPanel> GridPanel;
ChildSlot
[
SAssignNew(GridPanel, SGridPanel)
];
GridPanel->SetVisibility(EVisibility::Collapsed);
int32 RowIndex = 0;
// Add bound functions
auto AddFunctionBindingWidget = [this, InNode, &GridPanel, &RowIndex](FName InCategory, FName InMemberName)
{
GridPanel->SetVisibility(EVisibility::Visible);
// Find row
TSharedPtr<IPropertyHandle> PropertyHandle;
TSharedPtr<IDetailTreeNode> DetailTreeNode;
for (const TSharedRef<IDetailTreeNode>& RootTreeNode : PropertyRowGenerator->GetRootTreeNodes())
{
if(RootTreeNode->GetNodeName() == InCategory)
{
TArray<TSharedRef<IDetailTreeNode>> Children;
RootTreeNode->GetChildren(Children);
for (int32 ChildIdx = 0; ChildIdx < Children.Num(); ChildIdx++)
{
TSharedPtr<IPropertyHandle> ChildPropertyHandle = Children[ChildIdx]->CreatePropertyHandle();
if (ChildPropertyHandle.IsValid() && ChildPropertyHandle->GetProperty() && ChildPropertyHandle->GetProperty()->GetFName() == InMemberName)
{
DetailTreeNode = Children[ChildIdx];
PropertyHandle = ChildPropertyHandle;
PropertyHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda([InNode]()
{
InNode->ReconstructNode();
}));
break;
}
}
}
}
if(DetailTreeNode.IsValid() && PropertyHandle.IsValid())
{
DetailNodes.Add(DetailTreeNode);
FNodeWidgets NodeWidgets = DetailTreeNode->CreateNodeWidgets();
GridPanel->AddSlot(0, RowIndex)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(10.0f, 2.0f, 2.0f, 2.0f)
[
SNew(SLevelOfDetailBranchNode)
.UseLowDetailSlot(UseLowDetail)
.LowDetail()
[
SNew(SSpacer)
.Size(FVector2D(24.0f, 24.f))
]
.HighDetail()
[
NodeWidgets.NameWidget.ToSharedRef()
]
];
GridPanel->AddSlot(1, RowIndex)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(2.0f, 2.0f, 10.0f, 2.0f)
[
SNew(SLevelOfDetailBranchNode)
.UseLowDetailSlot(UseLowDetail)
.LowDetail()
[
SNew(SSpacer)
.Size(FVector2D(24.0f, 24.f))
]
.HighDetail()
[
NodeWidgets.ValueWidget.ToSharedRef()
]
];
RowIndex++;
}
};
if(InNode->InitialUpdateFunction.ResolveMember<UFunction>(InNode->GetBlueprintClassFromNode()) != nullptr)
{
AddFunctionBindingWidget("Functions", GET_MEMBER_NAME_CHECKED(UAnimGraphNode_Base, InitialUpdateFunction));
}
if(InNode->BecomeRelevantFunction.ResolveMember<UFunction>(InNode->GetBlueprintClassFromNode()) != nullptr)
{
AddFunctionBindingWidget("Functions", GET_MEMBER_NAME_CHECKED(UAnimGraphNode_Base, BecomeRelevantFunction));
}
if(InNode->UpdateFunction.ResolveMember<UFunction>(InNode->GetBlueprintClassFromNode()) != nullptr)
{
AddFunctionBindingWidget("Functions", GET_MEMBER_NAME_CHECKED(UAnimGraphNode_Base, UpdateFunction));
}
if(DetailNodes.Num() == 0)
{
// If we didnt add a function binding, remove the row generator as we dont need it and its expensive (as it ticks)
PropertyRowGenerator.Reset();
}
}
// Property row generator used to display function properties on nodes
TSharedPtr<IPropertyRowGenerator> PropertyRowGenerator;
// Hold a reference to the root ptr of the details tree we use to display function properties
TArray<TSharedPtr<IDetailTreeNode>> DetailNodes;
// Attribute allowing LOD
TAttribute<bool> UseLowDetail;
};
TSharedRef<SWidget> SAnimationGraphNode::CreateNodeFunctionsWidget(UAnimGraphNode_Base* InAnimNode, TAttribute<bool> InUseLowDetail)
{
return SNew(SAnimNodeFunctionsWidget, InAnimNode)
.UseLowDetail(InUseLowDetail);
}
TSharedRef<SWidget> SAnimationGraphNode::CreateNodeTagWidget(UAnimGraphNode_Base* InAnimNode, TAttribute<bool> InUseLowDetail)
{
return SNew(SLevelOfDetailBranchNode)
.Visibility_Lambda([InAnimNode](){ return (InAnimNode->Tag != NAME_None) ? EVisibility::Visible : EVisibility::Collapsed; })
.UseLowDetailSlot(InUseLowDetail)
.LowDetail()
[
SNew(SSpacer)
.Size(FVector2D(24.0f, 24.f))
]
.HighDetail()
[
SNew(SBox)
.Padding(FMargin(4.0f, 0.0f, 4.0f, 4.0f))
[
SNew(SInlineEditableTextBlock)
.ToolTipText_Lambda([InAnimNode](){ return FText::Format(LOCTEXT("TagFormat_Tooltip", "Tag: {0}\nThis node can be referenced elsewhere in this Anim Blueprint using this tag"), FText::FromName(InAnimNode->GetTag())); })
.Style(&FAppStyle::Get().GetWidgetStyle<FInlineEditableTextBlockStyle>("AnimGraph.Node.Tag"))
.Text_Lambda([InAnimNode](){ return FText::FromName(InAnimNode->GetTag()); })
.OnTextCommitted_Lambda([InAnimNode](const FText& InText, ETextCommit::Type InCommitType){ InAnimNode->SetTag(*InText.ToString()); })
]
];
}
void SAnimationGraphNode::ReconfigurePinWidgetsForPropertyBindings(UAnimGraphNode_Base* InAnimGraphNode, TSharedRef<SGraphNode> InGraphNodeWidget, TFunctionRef<TSharedPtr<SGraphPin>(UEdGraphPin*)> InFindWidgetForPin)
{
for(UEdGraphPin* Pin : InAnimGraphNode->Pins)
{
FEdGraphPinType PinType = Pin->PinType;
if(Pin->Direction == EGPD_Input && !UAnimationGraphSchema::IsPosePin(PinType))
{
TSharedPtr<SGraphPin> PinWidget = InFindWidgetForPin(Pin);
if(PinWidget.IsValid())
{
// Tweak padding a little to improve extended appearance
PinWidget->GetLabelAndValue()->SetInnerSlotPadding(FVector2D(2.0f, 0.0f));
TAttribute<bool> bIsEnabled = MakeAttributeLambda([WeakWidget = TWeakPtr<SGraphNode>(InGraphNodeWidget)]()
{
return WeakWidget.IsValid() ? WeakWidget.Pin()->IsNodeEditable() : false;
});
TSharedPtr<SWidget> PropertyBindingWidget = UAnimationGraphSchema::MakeBindingWidgetForPin({ InAnimGraphNode }, Pin->GetFName(), true, bIsEnabled);
if(PropertyBindingWidget.IsValid())
{
// Add binding widget
PinWidget->GetLabelAndValue()->AddSlot()
[
PropertyBindingWidget.ToSharedRef()
];
// Hide any value widgets when we have bindings
if(PinWidget->GetValueWidget() != SNullWidget::NullWidget)
{
PinWidget->GetValueWidget()->SetVisibility(MakeAttributeLambda([WeakPropertyBindingWidget = TWeakPtr<SWidget>(PropertyBindingWidget), WeakPinWidget = TWeakPtr<SGraphPin>(PinWidget)]()
{
EVisibility Visibility = EVisibility::Collapsed;
if (WeakPinWidget.IsValid())
{
Visibility = WeakPinWidget.Pin()->GetDefaultValueVisibility();
}
if(Visibility == EVisibility::Visible && WeakPropertyBindingWidget.IsValid())
{
Visibility = WeakPropertyBindingWidget.Pin()->GetVisibility() == EVisibility::Visible ? EVisibility::Collapsed : EVisibility::Visible;
}
return Visibility;
}));
}
}
}
}
}
}
#undef LOCTEXT_NAMESPACE