// Copyright Epic Games, Inc. All Rights Reserved. #include "StateMachineConnectionDrawingPolicy.h" #include "AnimStateEntryNode.h" #include "AnimStateNodeBase.h" #include "AnimStateTransitionNode.h" #include "AnimationStateNodes/SGraphNodeAnimTransition.h" #include "Containers/Set.h" #include "EdGraph/EdGraphPin.h" #include "Layout/ArrangedChildren.h" #include "Layout/ArrangedWidget.h" #include "Layout/PaintGeometry.h" #include "Math/Color.h" #include "Math/UnrealMathSSE.h" #include "Misc/Optional.h" #include "Rendering/DrawElements.h" #include "Rendering/RenderingCommon.h" #include "SGraphNode.h" #include "SGraphPin.h" #include "Styling/SlateBrush.h" #include "Templates/Casts.h" #include "Math/UnrealMathUtility.h" #include "Brushes/SlateRoundedBoxBrush.h" #include "Styling/StyleColors.h" class FSlateRect; class SWidget; struct FGeometry; ///////////////////////////////////////////////////// // FStateMachineConnectionDrawingPolicy FStateMachineConnectionDrawingPolicy::FStateMachineConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj) : FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements) , GraphObj(InGraphObj) { } void FStateMachineConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) { Params.bUserFlag2 = true; Params.AssociatedPin1 = OutputPin; Params.AssociatedPin2 = InputPin; Params.WireThickness = 1.5f; if (InputPin) { if (UAnimStateTransitionNode* TransNode = Cast(InputPin->GetOwningNode())) { const bool IsInputPinHovered = HoveredPins.Contains(InputPin); Params.WireColor = SGraphNodeAnimTransition::StaticGetTransitionColor(TransNode, IsInputPinHovered); Params.bUserFlag1 = TransNode->Bidirectional; } } const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0; if (bDeemphasizeUnhoveredPins) { ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor); } // Make the transition that is currently relinked, semi-transparent. for (const FRelinkConnection& Connection : RelinkConnections) { if (InputPin == nullptr || OutputPin == nullptr) { continue; } const FGraphPinHandle SourcePinHandle = Connection.SourcePin; const FGraphPinHandle TargetPinHandle = Connection.TargetPin; // Skip all transitions that don't start at the node our dragged and relink transition starts from if (OutputPin->GetOwningNode()->NodeGuid == SourcePinHandle.NodeGuid) { // Safety check to verify if the node is a transition node if (UAnimStateTransitionNode* TransitonNode = Cast(InputPin->GetOwningNode())) { if (UEdGraphPin* TransitionOutputPin = TransitonNode->GetOutputPin()) { if (TargetPinHandle.NodeGuid == TransitionOutputPin->GetOwningNode()->NodeGuid) { Params.WireColor.A *= 0.2f; } } } } } } void FStateMachineConnectionDrawingPolicy::DetermineLinkGeometry( FArrangedChildren& ArrangedNodes, TSharedRef& OutputPinWidget, UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*out*/ FArrangedWidget*& StartWidgetGeometry, /*out*/ FArrangedWidget*& EndWidgetGeometry ) { if (UAnimStateEntryNode* EntryNode = Cast(OutputPin->GetOwningNode())) { StartWidgetGeometry = PinGeometries->Find(OutputPinWidget); UAnimStateNodeBase* State = CastChecked(InputPin->GetOwningNode()); int32 StateIndex = NodeWidgetMap.FindChecked(State); EndWidgetGeometry = &(ArrangedNodes[StateIndex]); } else if (UAnimStateTransitionNode* TransNode = Cast(InputPin->GetOwningNode())) { UAnimStateNodeBase* PrevState = TransNode->GetPreviousState(); UAnimStateNodeBase* NextState = TransNode->GetNextState(); if ((PrevState != NULL) && (NextState != NULL)) { int32* PrevNodeIndex = NodeWidgetMap.Find(PrevState); int32* NextNodeIndex = NodeWidgetMap.Find(NextState); if ((PrevNodeIndex != NULL) && (NextNodeIndex != NULL)) { StartWidgetGeometry = &(ArrangedNodes[*PrevNodeIndex]); EndWidgetGeometry = &(ArrangedNodes[*NextNodeIndex]); } } } else { StartWidgetGeometry = PinGeometries->Find(OutputPinWidget); if (TSharedPtr* pTargetWidget = PinToPinWidgetMap.Find(InputPin)) { TSharedRef InputWidget = (*pTargetWidget).ToSharedRef(); EndWidgetGeometry = PinGeometries->Find(InputWidget); } } } void FStateMachineConnectionDrawingPolicy::Draw(TMap, FArrangedWidget>& InPinGeometries, FArrangedChildren& ArrangedNodes) { // Build an acceleration structure to quickly find geometry for the nodes NodeWidgetMap.Empty(); for (int32 NodeIndex = 0; NodeIndex < ArrangedNodes.Num(); ++NodeIndex) { FArrangedWidget& CurWidget = ArrangedNodes[NodeIndex]; TSharedRef ChildNode = StaticCastSharedRef(CurWidget.Widget); NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex); } // Now draw FConnectionDrawingPolicy::Draw(InPinGeometries, ArrangedNodes); } void FStateMachineConnectionDrawingPolicy::DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2f& StartPoint, const FVector2f& EndPoint, UEdGraphPin* Pin) { FConnectionParams Params; DetermineWiringStyle(Pin, nullptr, /*inout*/ Params); const FVector2f SeedPoint = EndPoint; const FVector2f AdjustedStartPoint = FGeometryHelper::FindClosestPointOnGeom(PinGeometry, SeedPoint); Params.bUserFlag2 = false; // bUserFlag2 is used to indicate whether the drawn arrow is a preview transition (the temporary transition when creating or relinking). DrawSplineWithArrow(AdjustedStartPoint, EndPoint, Params); } void FStateMachineConnectionDrawingPolicy::DrawSplineWithArrow(const FVector2f& StartAnchorPoint, const FVector2f& EndAnchorPoint, const FConnectionParams& Params) { Internal_DrawLineWithArrow(StartAnchorPoint, EndAnchorPoint, Params); // Is the connection bidirectional? if (Params.bUserFlag1) { Internal_DrawLineWithArrow(EndAnchorPoint, StartAnchorPoint, Params); } } void FStateMachineConnectionDrawingPolicy::Internal_DrawLineWithArrow(const FVector2f& StartAnchorPoint, const FVector2f& EndAnchorPoint, const FConnectionParams& Params) { //@TODO: Should this be scaled by zoom factor? const float LineSeparationAmount = 4.5f; const FVector2f DeltaPos = EndAnchorPoint - StartAnchorPoint; const FVector2f UnitDelta = DeltaPos.GetSafeNormal(); const FVector2f Normal = FVector2f(DeltaPos.Y, -DeltaPos.X).GetSafeNormal(); // Come up with the final start/end points const FVector2f DirectionBias = Normal * LineSeparationAmount; const FVector2f LengthBias = ArrowRadius.X * UnitDelta; const FVector2f StartPoint = StartAnchorPoint + DirectionBias + LengthBias; const FVector2f EndPoint = EndAnchorPoint + DirectionBias - LengthBias; FLinearColor ArrowHeadColor = Params.WireColor; // Draw a line/spline DrawConnection(WireLayerID, StartPoint, EndPoint, Params); const FVector2f ArrowDrawPos = EndPoint - ArrowRadius; const double AngleInRadians = FMath::Atan2(DeltaPos.Y, DeltaPos.X); // Draw the transition grab handles in case the mouse is hovering the transition bool StartHovered = false; bool EndHovered = false; const FVector2f FVecMousePos = FVector2f(LocalMousePosition.X, LocalMousePosition.Y); const FVector2f ClosestPoint = FMath::ClosestPointOnSegment2D(FVecMousePos, StartPoint, EndPoint); if ((ClosestPoint - FVecMousePos).Length() < RelinkHandleHoverRadius) { StartHovered = (StartPoint - LocalMousePosition).Length() < RelinkHandleHoverRadius; EndHovered = (EndPoint - LocalMousePosition).Length() < RelinkHandleHoverRadius; FVector2f HoverIndicatorPosition = StartHovered ? StartPoint : EndPoint; // Set the hovered pin results. This will be used by the SGraphPanel again. const float SquaredDistToPin1 = (Params.AssociatedPin1 != nullptr) ? (StartPoint - LocalMousePosition).SizeSquared() : FLT_MAX; const float SquaredDistToPin2 = (Params.AssociatedPin2 != nullptr) ? (EndPoint - LocalMousePosition).SizeSquared() : FLT_MAX; if (EndHovered) { SplineOverlapResult = FGraphSplineOverlapResult(Params.AssociatedPin1, Params.AssociatedPin2, SquaredDistToPin2, SquaredDistToPin1, SquaredDistToPin2, true); } // Draw grab handles only in case no relinking operation is performed if (RelinkConnections.IsEmpty() && Params.bUserFlag2) { if (EndHovered) { // Draw solid orange circle behind the arrow head in case the arrow head is hovered (area that enables a relink). FSlateRoundedBoxBrush RoundedBoxBrush = FSlateRoundedBoxBrush( FLinearColor(0.0f, 0.0f, 0.0f, 0.0f), 9.0f, FStyleColors::AccentOrange, 100.0f); FSlateDrawElement::MakeBox(DrawElementsList, ArrowLayerID-1, // Draw behind the arrow FPaintGeometry(EndPoint - ArrowRadius, BubbleImage->ImageSize * ZoomFactor, ZoomFactor), &RoundedBoxBrush); ArrowHeadColor = FLinearColor::Black; } else { // Draw circle around the arrow in case the transition is hovered (mouse close or over transition line or arrow head). const int CircleLineSegments = 16; const float CircleRadius = 10.0f; const FVector2f CircleCenter = EndPoint - UnitDelta * 2.0f; DrawCircle(CircleCenter, CircleRadius, Params.WireColor, CircleLineSegments); } } } // Draw the number of relinked transitions on the preview transition. if (!RelinkConnections.IsEmpty() && !Params.bUserFlag2) { // Get the number of actually relinked transitions. int32 NumRelinkedTransitions = 0; for (const FRelinkConnection& Connection : RelinkConnections) { NumRelinkedTransitions += UAnimStateTransitionNode::GetListTransitionNodesToRelink(Connection.SourcePin, Connection.TargetPin, SelectedGraphNodes).Num(); if (UAnimStateEntryNode* EntryNode = Cast(Connection.SourcePin->GetOwningNode())) { NumRelinkedTransitions += 1; } } const FVector2f TransitionCenter = StartAnchorPoint + DeltaPos * 0.5f; const FVector2f TextPosition = TransitionCenter + Normal * 15.0f * ZoomFactor; FSlateDrawElement::MakeText( DrawElementsList, ArrowLayerID, FPaintGeometry(TextPosition, ArrowImage->ImageSize * ZoomFactor, ZoomFactor), FText::AsNumber(NumRelinkedTransitions), FCoreStyle::Get().GetFontStyle("SmallFont")); } // Draw the transition arrow triangle FSlateDrawElement::MakeRotatedBox( DrawElementsList, ArrowLayerID, FPaintGeometry(ArrowDrawPos, ArrowImage->ImageSize * ZoomFactor, ZoomFactor), ArrowImage, ESlateDrawEffect::None, static_cast(AngleInRadians), TOptional(), FSlateDrawElement::RelativeToElement, ArrowHeadColor ); } void FStateMachineConnectionDrawingPolicy::DrawCircle(const FVector2f& Center, float Radius, const FLinearColor& Color, const int NumLineSegments) { TempPoints.Empty(); const float NumFloatLineSegments = (float)NumLineSegments; for (int i = 0; i <= NumLineSegments; i++) { const float Angle = (i / NumFloatLineSegments) * TWO_PI; FVector2f PointOnCircle; PointOnCircle.X = cosf(Angle) * Radius; PointOnCircle.Y = sinf(Angle) * Radius; TempPoints.Add(PointOnCircle); } FSlateDrawElement::MakeLines( DrawElementsList, ArrowLayerID + 1, FPaintGeometry(Center, FVector2f(Radius, Radius) * ZoomFactor, ZoomFactor), TempPoints, ESlateDrawEffect::None, Color ); } void FStateMachineConnectionDrawingPolicy::DrawSplineWithArrow(const FGeometry& StartGeom, const FGeometry& EndGeom, const FConnectionParams& Params) { // Get a reasonable seed point (halfway between the boxes) const FVector2f StartCenter = FGeometryHelper::CenterOf(StartGeom); const FVector2f EndCenter = FGeometryHelper::CenterOf(EndGeom); const FVector2f SeedPoint = (StartCenter + EndCenter) * 0.5f; // Find the (approximate) closest points between the two boxes const FVector2f StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint); const FVector2f EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint); DrawSplineWithArrow(StartAnchorPoint, EndAnchorPoint, Params); } FVector2f FStateMachineConnectionDrawingPolicy::ComputeSplineTangent(const FVector2f& Start, const FVector2f& End) const { const FVector2f Delta = End - Start; const FVector2f NormDelta = Delta.GetSafeNormal(); return NormDelta; }