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

441 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimationStateNodes/SGraphNodeAnimState.h"
#include "AnimStateConduitNode.h"
#include "AnimStateNodeBase.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/AnimBlueprintGeneratedClass.h"
#include "Animation/AnimInstance.h"
#include "Containers/Map.h"
#include "Delegates/Delegate.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Engine/Blueprint.h"
#include "GenericPlatform/ICursor.h"
#include "HAL/PlatformCrt.h"
#include "IDocumentation.h"
#include "Internationalization/Internationalization.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Layout/Margin.h"
#include "Layout/Visibility.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/Optional.h"
#include "SGraphPanel.h"
#include "SGraphPin.h"
#include "SGraphPreviewer.h"
#include "SNodePanel.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "Types/SlateEnums.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Notifications/SErrorText.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Text/STextBlock.h"
class SWidget;
class UEdGraphSchema;
struct FGeometry;
struct FPointerEvent;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "SGraphNodeAnimState"
/////////////////////////////////////////////////////
// SStateMachineOutputPin
class SStateMachineOutputPin : public SGraphPin
{
public:
SLATE_BEGIN_ARGS(SStateMachineOutputPin){}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdGraphPin* InPin);
protected:
// Begin SGraphPin interface
virtual TSharedRef<SWidget> GetDefaultValueWidget() override;
// End SGraphPin interface
const FSlateBrush* GetPinBorder() const;
};
void SStateMachineOutputPin::Construct(const FArguments& InArgs, UEdGraphPin* InPin)
{
this->SetCursor( EMouseCursor::Default );
bShowLabel = true;
GraphPinObj = InPin;
check(GraphPinObj != NULL);
const UEdGraphSchema* Schema = GraphPinObj->GetSchema();
check(Schema);
// Set up a hover for pins that is tinted the color of the pin.
SBorder::Construct( SBorder::FArguments()
.BorderImage( this, &SStateMachineOutputPin::GetPinBorder )
.BorderBackgroundColor( this, &SStateMachineOutputPin::GetPinColor )
.OnMouseButtonDown( this, &SStateMachineOutputPin::OnPinMouseDown )
.Cursor( this, &SStateMachineOutputPin::GetPinCursor )
);
}
TSharedRef<SWidget> SStateMachineOutputPin::GetDefaultValueWidget()
{
return SNew(STextBlock);
}
const FSlateBrush* SStateMachineOutputPin::GetPinBorder() const
{
return ( IsHovered() )
? FAppStyle::GetBrush( TEXT("Graph.StateNode.Pin.BackgroundHovered") )
: FAppStyle::GetBrush( TEXT("Graph.StateNode.Pin.Background") );
}
/////////////////////////////////////////////////////
// SGraphNodeAnimState
void SGraphNodeAnimState::Construct(const FArguments& InArgs, UAnimStateNodeBase* InNode)
{
this->GraphNode = InNode;
this->SetCursor(EMouseCursor::CardinalCross);
this->UpdateGraphNode();
}
void SGraphNodeAnimState::GetStateInfoPopup(UEdGraphNode* GraphNode, TArray<FGraphInformationPopupInfo>& Popups)
{
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(GraphNode));
if(AnimBlueprint)
{
UAnimInstance* ActiveObject = Cast<UAnimInstance>(AnimBlueprint->GetObjectBeingDebugged());
UAnimBlueprintGeneratedClass* Class = AnimBlueprint->GetAnimBlueprintGeneratedClass();
FLinearColor CurrentStateColor(1.f, 0.5f, 0.25f);
// Display various types of debug data
if ((ActiveObject != NULL) && (Class != NULL))
{
if (Class->GetAnimNodeProperties().Num())
{
if (FStateMachineDebugData* DebugInfo = Class->GetAnimBlueprintDebugData().StateMachineDebugData.Find(GraphNode->GetGraph()))
{
if(int32* StateIndexPtr = DebugInfo->NodeToStateIndex.Find(GraphNode))
{
for(const FStateMachineStateDebugData& StateData : Class->GetAnimBlueprintDebugData().StateData)
{
if(StateData.StateMachineIndex == DebugInfo->MachineIndex && StateData.StateIndex == *StateIndexPtr)
{
if (StateData.Weight > 0.0f)
{
FText StateText;
if (StateData.ElapsedTime > 0.0f)
{
StateText = FText::Format(LOCTEXT("ActiveStateWeightFormat", "{0}\nActive for {1}s"), FText::AsPercent(StateData.Weight), FText::AsNumber(StateData.ElapsedTime));
}
else
{
StateText = FText::Format(LOCTEXT("StateWeightFormat", "{0}"), FText::AsPercent(StateData.Weight));
}
Popups.Emplace(nullptr, CurrentStateColor, StateText.ToString());
break;
}
}
}
}
}
}
}
}
}
void SGraphNodeAnimState::GetNodeInfoPopups(FNodeInfoContext* Context, TArray<FGraphInformationPopupInfo>& Popups) const
{
GetStateInfoPopup(GraphNode, Popups);
}
FSlateColor SGraphNodeAnimState::GetBorderBackgroundColor() const
{
FLinearColor InactiveStateColor(0.08f, 0.08f, 0.08f);
FLinearColor ActiveStateColorDim(0.4f, 0.3f, 0.15f);
FLinearColor ActiveStateColorBright(1.f, 0.6f, 0.35f);
return GetBorderBackgroundColor_Internal(InactiveStateColor, ActiveStateColorDim, ActiveStateColorBright);
}
FSlateColor SGraphNodeAnimState::GetBorderBackgroundColor_Internal(FLinearColor InactiveStateColor, FLinearColor ActiveStateColorDim, FLinearColor ActiveStateColorBright) const
{
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(GraphNode));
if(AnimBlueprint)
{
UAnimInstance* ActiveObject = Cast<UAnimInstance>(AnimBlueprint->GetObjectBeingDebugged());
UAnimBlueprintGeneratedClass* Class = AnimBlueprint->GetAnimBlueprintGeneratedClass();
// Display various types of debug data
if ((ActiveObject != NULL) && (Class != NULL))
{
if (FStateMachineDebugData* DebugInfo = Class->GetAnimBlueprintDebugData().StateMachineDebugData.Find(GraphNode->GetGraph()))
{
if(int32* StateIndexPtr = DebugInfo->NodeToStateIndex.Find(GraphNode))
{
for(const FStateMachineStateDebugData& StateData : Class->GetAnimBlueprintDebugData().StateData)
{
if(StateData.StateMachineIndex == DebugInfo->MachineIndex && StateData.StateIndex == *StateIndexPtr)
{
if (StateData.Weight > 0.0f)
{
return FMath::Lerp<FLinearColor>(ActiveStateColorDim, ActiveStateColorBright, StateData.Weight);
}
}
}
}
}
}
}
return InactiveStateColor;
}
void SGraphNodeAnimState::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
// Add pins to the hover set so outgoing transitions arrows remains highlighted while the mouse is over the state node
if (const UAnimStateNodeBase* StateNode = Cast<UAnimStateNodeBase>(GraphNode))
{
if (const UEdGraphPin* OutputPin = StateNode->GetOutputPin())
{
TSharedPtr<SGraphPanel> OwnerPanel = GetOwnerPanel();
check(OwnerPanel.IsValid());
for (int32 LinkIndex = 0; LinkIndex < OutputPin->LinkedTo.Num(); ++LinkIndex)
{
OwnerPanel->AddPinToHoverSet(OutputPin->LinkedTo[LinkIndex]);
}
}
}
SGraphNode::OnMouseEnter(MyGeometry, MouseEvent);
}
void SGraphNodeAnimState::OnMouseLeave(const FPointerEvent& MouseEvent)
{
// Remove manually added pins from the hover set
if (const UAnimStateNodeBase* StateNode = Cast<UAnimStateNodeBase>(GraphNode))
{
if(const UEdGraphPin* OutputPin = StateNode->GetOutputPin())
{
TSharedPtr<SGraphPanel> OwnerPanel = GetOwnerPanel();
check(OwnerPanel.IsValid());
for (int32 LinkIndex = 0; LinkIndex < OutputPin->LinkedTo.Num(); ++LinkIndex)
{
OwnerPanel->RemovePinFromHoverSet(OutputPin->LinkedTo[LinkIndex]);
}
}
}
SGraphNode::OnMouseLeave(MouseEvent);
}
void SGraphNodeAnimState::UpdateGraphNode()
{
InputPins.Empty();
OutputPins.Empty();
// Reset variables that are going to be exposed, in case we are refreshing an already setup node.
RightNodeBox.Reset();
LeftNodeBox.Reset();
const FSlateBrush* NodeTypeIcon = GetNameIcon();
FLinearColor TitleShadowColor(0.6f, 0.6f, 0.6f);
TSharedPtr<SErrorText> ErrorText;
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
this->ContentScale.Bind( this, &SGraphNode::GetContentScale );
this->GetOrAddSlot( ENodeZone::Center )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage( FAppStyle::GetBrush( "Graph.StateNode.Body" ) )
.Padding(0)
.BorderBackgroundColor( this, &SGraphNodeAnimState::GetBorderBackgroundColor )
[
SNew(SOverlay)
// PIN AREA
+SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SAssignNew(RightNodeBox, SVerticalBox)
]
// STATE NAME AREA
+SOverlay::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(10.0f)
[
SNew(SBorder)
.BorderImage( FAppStyle::GetBrush("Graph.StateNode.ColorSpill") )
.BorderBackgroundColor( TitleShadowColor )
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Visibility(EVisibility::SelfHitTestInvisible)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
// POPUP ERROR MESSAGE
SAssignNew(ErrorText, SErrorText )
.BackgroundColor( this, &SGraphNodeAnimState::GetErrorColor )
.ToolTipText( this, &SGraphNodeAnimState::GetErrorMsgToolTip )
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(NodeTypeIcon)
]
+SHorizontalBox::Slot()
.Padding(FMargin(4.0f, 0.0f, 4.0f, 0.0f))
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(InlineEditableText, SInlineEditableTextBlock)
.Style( FAppStyle::Get(), "Graph.StateNode.NodeTitleInlineEditableText" )
.Text( NodeTitle.Get(), &SNodeTitle::GetHeadTitle )
.OnVerifyTextChanged(this, &SGraphNodeAnimState::OnVerifyNameTextChanged)
.OnTextCommitted(this, &SGraphNodeAnimState::OnNameTextCommited)
.IsReadOnly( this, &SGraphNodeAnimState::IsNameReadOnly )
.IsSelected(this, &SGraphNodeAnimState::IsSelectedExclusively)
]
+SVerticalBox::Slot()
.AutoHeight()
[
NodeTitle.ToSharedRef()
]
]
]
]
]
];
ErrorReporting = ErrorText;
ErrorReporting->SetError(ErrorMsg);
CreatePinWidgets();
}
void SGraphNodeAnimState::CreatePinWidgets()
{
UAnimStateNodeBase* StateNode = CastChecked<UAnimStateNodeBase>(GraphNode);
UEdGraphPin* CurPin = StateNode->GetOutputPin();
if (!CurPin->bHidden)
{
TSharedPtr<SGraphPin> NewPin = SNew(SStateMachineOutputPin, CurPin);
this->AddPin(NewPin.ToSharedRef());
}
}
void SGraphNodeAnimState::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner( SharedThis(this) );
RightNodeBox->AddSlot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.FillHeight(1.0f)
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
TSharedPtr<SToolTip> SGraphNodeAnimState::GetComplexTooltip()
{
UAnimStateNodeBase* StateNode = CastChecked<UAnimStateNodeBase>(GraphNode);
return SNew(SToolTip)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
// Create the tooltip preview, ensure to disable state overlays to stop
// PIE and read-only borders obscuring the graph
SNew(SGraphPreviewer, StateNode->GetBoundGraph())
.CornerOverlayText(this, &SGraphNodeAnimState::GetPreviewCornerText)
.ShowGraphStateOverlay(false)
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 5.0f, 0.0f, 0.0f))
[
IDocumentation::Get()->CreateToolTip(FText::FromString("Documentation"), NULL, StateNode->GetDocumentationLink(), StateNode->GetDocumentationExcerptName())
]
];
}
FText SGraphNodeAnimState::GetPreviewCornerText() const
{
UAnimStateNodeBase* StateNode = CastChecked<UAnimStateNodeBase>(GraphNode);
return FText::Format(NSLOCTEXT("SGraphNodeAnimState", "PreviewCornerStateText", "{0} state"), FText::FromString(StateNode->GetStateName()));
}
const FSlateBrush* SGraphNodeAnimState::GetNameIcon() const
{
return FAppStyle::GetBrush( TEXT("Graph.StateNode.Icon") );
}
/////////////////////////////////////////////////////
// SGraphNodeAnimConduit
void SGraphNodeAnimConduit::Construct(const FArguments& InArgs, UAnimStateConduitNode* InNode)
{
SGraphNodeAnimState::Construct(SGraphNodeAnimState::FArguments(), InNode);
}
void SGraphNodeAnimConduit::GetNodeInfoPopups(FNodeInfoContext* Context, TArray<FGraphInformationPopupInfo>& Popups) const
{
// Intentionally empty.
}
FSlateColor SGraphNodeAnimConduit::GetBorderBackgroundColor_Internal(FLinearColor InactiveStateColor, FLinearColor ActiveStateColorDim, FLinearColor ActiveStateColorBright) const
{
// Override inactive state color for conduits.
return SGraphNodeAnimState::GetBorderBackgroundColor_Internal(FLinearColor(0.38f, 0.45f, 0.21f), ActiveStateColorDim, ActiveStateColorBright);
}
FText SGraphNodeAnimConduit::GetPreviewCornerText() const
{
UAnimStateNodeBase* StateNode = CastChecked<UAnimStateNodeBase>(GraphNode);
return FText::Format(NSLOCTEXT("SGraphNodeAnimState", "PreviewCornerConduitText", "{0} conduit"), FText::FromString(StateNode->GetStateName()));
}
const FSlateBrush* SGraphNodeAnimConduit::GetNameIcon() const
{
return FAppStyle::GetBrush( TEXT("Graph.ConduitNode.Icon") );
}
#undef LOCTEXT_NAMESPACE