// 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 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 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& Popups) { UAnimBlueprint* AnimBlueprint = Cast(FBlueprintEditorUtils::FindBlueprintForNode(GraphNode)); if(AnimBlueprint) { UAnimInstance* ActiveObject = Cast(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& 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(FBlueprintEditorUtils::FindBlueprintForNode(GraphNode)); if(AnimBlueprint) { UAnimInstance* ActiveObject = Cast(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(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(GraphNode)) { if (const UEdGraphPin* OutputPin = StateNode->GetOutputPin()) { TSharedPtr 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(GraphNode)) { if(const UEdGraphPin* OutputPin = StateNode->GetOutputPin()) { TSharedPtr 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 ErrorText; TSharedPtr 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(GraphNode); UEdGraphPin* CurPin = StateNode->GetOutputPin(); if (!CurPin->bHidden) { TSharedPtr NewPin = SNew(SStateMachineOutputPin, CurPin); this->AddPin(NewPin.ToSharedRef()); } } void SGraphNodeAnimState::AddPin(const TSharedRef& PinToAdd) { PinToAdd->SetOwner( SharedThis(this) ); RightNodeBox->AddSlot() .HAlign(HAlign_Fill) .VAlign(VAlign_Fill) .FillHeight(1.0f) [ PinToAdd ]; OutputPins.Add(PinToAdd); } TSharedPtr SGraphNodeAnimState::GetComplexTooltip() { UAnimStateNodeBase* StateNode = CastChecked(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(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& 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(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