// Copyright Epic Games, Inc. All Rights Reserved. #include "SceneStateMachineConnectionDrawingPolicy.h" #include "Brushes/SlateRoundedBoxBrush.h" #include "Nodes/SceneStateMachineEntryNode.h" #include "Nodes/SceneStateMachineExitNode.h" #include "Nodes/SceneStateMachineTaskNode.h" #include "Nodes/SceneStateMachineTransitionNode.h" #include "SceneStateMachineEditorStyle.h" namespace UE::SceneState::Editor { namespace Private { bool IsTaskConnection(UEdGraphPin* InOutputPin, UEdGraphPin* InInputPin) { UEdGraphNode* SourceNode = InInputPin ? InInputPin->GetOwningNodeUnchecked() : nullptr; UEdGraphNode* TargetNode = InOutputPin ? InOutputPin->GetOwningNodeUnchecked() : nullptr; return Cast(SourceNode) || Cast(TargetNode); } } // Private FStateMachineConnectionDrawingPolicy::FStateMachineConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements) : FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements) { } void FStateMachineConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* InOutputPin, UEdGraphPin* InInputPin, FConnectionParams& InOutParams) { InOutParams.bUserFlag2 = true; InOutParams.AssociatedPin1 = InOutputPin; InOutParams.AssociatedPin2 = InInputPin; if (HoveredPins.Num() > 0) { ApplyHoverDeemphasis(InOutputPin, InInputPin, InOutParams.WireThickness, InOutParams.WireColor); } // Task Wiring Style if (Private::IsTaskConnection(InOutputPin, InInputPin)) { InOutParams.WireThickness = 1.5f; InOutParams.WireColor = FStateMachineEditorStyle::Get().GetColor("WireColor.Task"); } // Transition Wiring Style else { InOutParams.WireThickness = 2.f; InOutParams.WireColor = FStateMachineEditorStyle::Get().GetColor("WireColor.Transition"); } // Make the transition that is currently relinked, semi-transparent. for (const FRelinkConnection& Connection : RelinkConnections) { if (!InInputPin || !InOutputPin) { 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 (InOutputPin->GetOwningNode()->NodeGuid == SourcePinHandle.NodeGuid) { // Safety check to verify if the node is a transition node if (USceneStateMachineTransitionNode* TransitionNode = Cast(InInputPin->GetOwningNode())) { if (UEdGraphPin* TransitionOutputPin = TransitionNode->GetOutputPin()) { if (TargetPinHandle.NodeGuid == TransitionOutputPin->GetOwningNode()->NodeGuid) { InOutParams.WireColor.A *= 0.2f; } } } } } } void FStateMachineConnectionDrawingPolicy::Draw(TMap, FArrangedWidget>& InPinGeometries, FArrangedChildren& InArrangedNodes) { const int32 ArrangeNodeCount = InArrangedNodes.Num(); // Build an acceleration structure to quickly find geometry for the nodes NodeWidgetMap.Empty(ArrangeNodeCount); for (int32 NodeIndex = 0; NodeIndex < ArrangeNodeCount; ++NodeIndex) { TSharedRef ChildNode = StaticCastSharedRef(InArrangedNodes[NodeIndex].Widget); NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex); } FConnectionDrawingPolicy::Draw(InPinGeometries, InArrangedNodes); } void FStateMachineConnectionDrawingPolicy::DetermineLinkGeometry(FArrangedChildren& InArrangedNodes , TSharedRef& InOutputPinWidget , UEdGraphPin* InOutputPin , UEdGraphPin* InInputPin , FArrangedWidget*& OutStartWidgetGeometry , FArrangedWidget*& OutEndWidgetGeometry) { if (USceneStateMachineEntryNode* const EntryNode = Cast(InOutputPin->GetOwningNode())) { OutStartWidgetGeometry = PinGeometries->Find(InOutputPinWidget); USceneStateMachineNode* const Node = CastChecked(InInputPin->GetOwningNode()); const int32 NodeIndex = NodeWidgetMap.FindChecked(Node); OutEndWidgetGeometry = &(InArrangedNodes[NodeIndex]); } else if (USceneStateMachineExitNode* const ExitNode = Cast(InInputPin->GetOwningNode())) { OutStartWidgetGeometry = PinGeometries->Find(InOutputPinWidget); USceneStateMachineNode* const Node = CastChecked(InOutputPin->GetOwningNode()); const int32 NodeIndex = NodeWidgetMap.FindChecked(Node); OutEndWidgetGeometry = &(InArrangedNodes[NodeIndex]); } else if (USceneStateMachineTransitionNode* const TransitionNode = Cast(InInputPin->GetOwningNode())) { USceneStateMachineNode* const SourceState = TransitionNode->GetSourceNode(); USceneStateMachineNode* const TargetState = TransitionNode->GetTargetNode(); if (SourceState && TargetState) { const int32* SourceStateIndex = NodeWidgetMap.Find(SourceState); const int32* TargetStateIndex = NodeWidgetMap.Find(TargetState); if (SourceStateIndex && TargetStateIndex) { OutStartWidgetGeometry = &(InArrangedNodes[*SourceStateIndex]); OutEndWidgetGeometry = &(InArrangedNodes[*TargetStateIndex]); } } } else { USceneStateMachineNode* SourceNode = Cast(InOutputPin->GetOwningNode()); USceneStateMachineNode* TargetNode = Cast(InInputPin->GetOwningNode()); const int32* SourceNodeIndex = NodeWidgetMap.Find(SourceNode); const int32* TargetNodeIndex = NodeWidgetMap.Find(TargetNode); if (SourceNodeIndex && TargetNodeIndex) { OutStartWidgetGeometry = &(InArrangedNodes[*SourceNodeIndex]); OutEndWidgetGeometry = &(InArrangedNodes[*TargetNodeIndex]); } } } void FStateMachineConnectionDrawingPolicy::DrawSplineWithArrow(const FGeometry& InStartGeometry, const FGeometry& InEndGeometry, const FConnectionParams& InParams) { // Get a reasonable seed point (halfway between the boxes) const FVector2f StartCenter = FGeometryHelper::CenterOf(InStartGeometry); const FVector2f EndCenter = FGeometryHelper::CenterOf(InEndGeometry); const FVector2f SeedPoint = (StartCenter + EndCenter) * 0.5f; // Find the (approximate) closest points between the two boxes const FVector2f StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(InStartGeometry, SeedPoint); const FVector2f EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(InEndGeometry, SeedPoint); const float LineSeparationAmount = 4.f; const FVector2f DeltaPosition = EndAnchorPoint - StartAnchorPoint; const FVector2f DeltaNormal = FVector2f(DeltaPosition.Y, -DeltaPosition.X).GetSafeNormal(); // Come up with the final start/end points const FVector2f DirectionBias = DeltaNormal * LineSeparationAmount; DrawSplineWithArrow(StartAnchorPoint + DirectionBias, EndAnchorPoint + DirectionBias, InParams); } void FStateMachineConnectionDrawingPolicy::DrawSplineWithArrow(const FVector2f& InStartPoint, const FVector2f& InEndPoint, const FConnectionParams& InParams) { DrawArrowLine(InStartPoint, InEndPoint, InParams); } void FStateMachineConnectionDrawingPolicy::DrawPreviewConnector(const FGeometry& InPinGeometry, const FVector2f& InStartPoint, const FVector2f& InEndPoint, UEdGraphPin* InPin) { FConnectionParams Params; DetermineWiringStyle(InPin, nullptr, Params); // bUserFlag2 is used to indicate whether the drawn arrow is a preview transition (the temporary transition when creating or relinking). Params.bUserFlag2 = false; DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(InPinGeometry, InEndPoint), InEndPoint, Params); } FVector2f FStateMachineConnectionDrawingPolicy::ComputeSplineTangent(const FVector2f& InStart, const FVector2f& InEnd) const { return (InEnd - InStart).GetSafeNormal(); } void FStateMachineConnectionDrawingPolicy::DrawArrowLine(const FVector2f& InStartPoint, const FVector2f& InEndPoint, const FConnectionParams& InParams) { const bool bIsTaskConnection = Private::IsTaskConnection(InParams.AssociatedPin1, InParams.AssociatedPin2); const float ArrowScale = bIsTaskConnection ? 0.75f : 1.0f; const FVector2f ScaledArrowRadius = ArrowRadius * ArrowScale; const FVector2f DeltaPosition = InEndPoint - InStartPoint; const FVector2f UnitDelta = DeltaPosition.GetSafeNormal(); const FVector2f EndPoint = InEndPoint - (ScaledArrowRadius.X * UnitDelta); const FVector2f ArrowPosition = EndPoint - ScaledArrowRadius; FLinearColor ArrowHeadColor = InParams.WireColor; // Draw a line/spline DrawConnection(WireLayerID, InStartPoint, EndPoint, InParams); // Draw the transition grab handles in case the mouse is hovering the transition const FVector2f ClosestPoint = FMath::ClosestPointOnSegment2D(LocalMousePosition, InStartPoint, EndPoint); constexpr double RelinkHandleHoverRadius = 20.0; if ((ClosestPoint - LocalMousePosition).Length() < RelinkHandleHoverRadius) { const double RelinkHoverRadiusSq = FMath::Square(RelinkHandleHoverRadius); const double StartMouseSizeSq = FVector2f(InStartPoint - LocalMousePosition).SizeSquared(); const double EndMouseSizeSq = FVector2f(EndPoint - LocalMousePosition).SizeSquared(); if (EndMouseSizeSq < RelinkHoverRadiusSq) { // Set the hovered pin results. This will be used by the SGraphPanel again. const double SquaredDistToPin1 = InParams.AssociatedPin1 ? StartMouseSizeSq : TNumericLimits::Max(); const double SquaredDistToPin2 = InParams.AssociatedPin2 ? EndMouseSizeSq : TNumericLimits::Max(); SplineOverlapResult = FGraphSplineOverlapResult(InParams.AssociatedPin1, InParams.AssociatedPin2, SquaredDistToPin2, SquaredDistToPin1, SquaredDistToPin2, true); } // Draw grab handles only in case no relinking operation is performed if (RelinkConnections.IsEmpty() && InParams.bUserFlag2) { if (EndMouseSizeSq < RelinkHoverRadiusSq) { // Draw solid orange circle behind the arrow head in case the arrow head is hovered (area that enables a relink). const 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(ArrowPosition, BubbleImage->ImageSize * ZoomFactor * ArrowScale, 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 FVector2f CircleCenter = EndPoint - UnitDelta * 2.0; DrawCircle(CircleCenter, /*Radius*/10.f * ArrowScale, InParams.WireColor, /*Segments*/16); } } } const double ArrowAngle = FMath::Atan2(DeltaPosition.Y, DeltaPosition.X); // Draw the transition arrow triangle FSlateDrawElement::MakeRotatedBox(DrawElementsList , ArrowLayerID , FPaintGeometry(ArrowPosition, ScaledArrowRadius * 2.0, ZoomFactor) , ArrowImage , ESlateDrawEffect::None , ArrowAngle , TOptional() , FSlateDrawElement::RelativeToElement , ArrowHeadColor ); } void FStateMachineConnectionDrawingPolicy::DrawCircle(const FVector2f& InCenter, float InRadius, const FLinearColor& InColor, int32 InSegments) { check(InSegments > 0); const float SegmentWeight = 1.f / static_cast(InSegments); TArray Points; Points.Reserve(InSegments + 1); for (int32 Index = 0; Index <= InSegments; ++Index) { const float Angle = Index * SegmentWeight * TWO_PI; Points.Emplace(FMath::Cos(Angle) * InRadius, FMath::Sin(Angle) * InRadius); } FSlateDrawElement::MakeLines(DrawElementsList , ArrowLayerID + 1 , FPaintGeometry(InCenter, FVector2f(InRadius, InRadius) * ZoomFactor, ZoomFactor) , Points , ESlateDrawEffect::None , InColor ); } } // UE::SceneState::Editor