// Copyright Epic Games, Inc. All Rights Reserved. #include "ConnectionDrawingPolicy.h" #include "SGraphPanel.h" #include "Rendering/DrawElements.h" #include "Widgets/SToolTip.h" #include "Framework/Application/SlateApplication.h" DEFINE_LOG_CATEGORY(LogConnectionDrawingPolicy); ////////////////////////////////////////////////////////////////////////// // FGeometryHelper FVector2D FGeometryHelper::VerticalMiddleLeftOf(const FGeometry& SomeGeometry) { const FVector2D GeometryDrawSize = SomeGeometry.GetDrawSize(); return FVector2D( SomeGeometry.AbsolutePosition.X, SomeGeometry.AbsolutePosition.Y + GeometryDrawSize.Y/2 ); } FVector2D FGeometryHelper::VerticalMiddleRightOf(const FGeometry& SomeGeometry) { const FVector2D GeometryDrawSize = SomeGeometry.GetDrawSize(); return FVector2D( SomeGeometry.AbsolutePosition.X + GeometryDrawSize.X, SomeGeometry.AbsolutePosition.Y + GeometryDrawSize.Y/2 ); } FVector2D FGeometryHelper::CenterOf(const FGeometry& SomeGeometry) { const FVector2D GeometryDrawSize = SomeGeometry.GetDrawSize(); return FVector2D(SomeGeometry.AbsolutePosition) + (GeometryDrawSize * 0.5f); } void FGeometryHelper::ConvertToPoints(const FGeometry& Geom, TArray& Points) { const FVector2D Size = Geom.GetDrawSize(); const FVector2D Location = FVector2D(Geom.AbsolutePosition); int32 Index = Points.AddUninitialized(4); Points[Index++] = Location; Points[Index++] = Location + FVector2D(0.0f, Size.Y); Points[Index++] = Location + FVector2D(Size.X, Size.Y); Points[Index++] = Location + FVector2D(Size.X, 0.0f); } /** Find the point on line segment from LineStart to LineEnd which is closest to Point */ FVector2D FGeometryHelper::FindClosestPointOnLine(const FVector2D& LineStart, const FVector2D& LineEnd, const FVector2D& TestPoint) { const FVector2D LineVector = LineEnd - LineStart; const float A = -FVector2D::DotProduct(LineStart - TestPoint, LineVector); const float B = LineVector.SizeSquared(); const float T = FMath::Clamp(A / B, 0.0f, 1.0f); // Generate closest point return LineStart + (T * LineVector); } FVector2D FGeometryHelper::FindClosestPointOnGeom(const FGeometry& Geom, const FVector2D& TestPoint) { TArray Points; FGeometryHelper::ConvertToPoints(Geom, Points); float BestDistanceSquared = MAX_FLT; FVector2D BestPoint = FVector2D::ZeroVector; for (int32 i = 0; i < Points.Num(); ++i) { const FVector2D Candidate = FindClosestPointOnLine(Points[i], Points[(i + 1) % Points.Num()], TestPoint); const float CandidateDistanceSquared = (Candidate-TestPoint).SizeSquared(); if (CandidateDistanceSquared < BestDistanceSquared) { BestPoint = Candidate; BestDistanceSquared = CandidateDistanceSquared; } } return BestPoint; } ///////////////////////////////////////////////////// // FConnectionDrawingPolicy FConnectionDrawingPolicy::FConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements) : WireLayerID(InBackLayerID) , ArrowLayerID(InFrontLayerID) , Settings(GetDefault()) , ZoomFactor(InZoomFactor) , ClippingRect(InClippingRect) , DrawElementsList(InDrawElements) , LocalMousePosition(0.0f, 0.0f) { ArrowImage = FAppStyle::GetBrush( TEXT("Graph.Arrow") ); ArrowRadius = ArrowImage->ImageSize * ZoomFactor * 0.5f; MidpointImage = nullptr; MidpointRadius = FVector2D::ZeroVector; HoverDeemphasisDarkFraction = 0.8f; BubbleImage = FAppStyle::GetBrush( TEXT("Graph.ExecutionBubble") ); } void FConnectionDrawingPolicy::DrawSplineWithArrow(const FVector2D& StartPoint, const FVector2D& EndPoint, const FConnectionParams& Params) { // Draw the spline DrawConnection( WireLayerID, StartPoint, EndPoint, Params); // Draw the arrow if (ArrowImage != nullptr) { FVector2D ArrowPoint = EndPoint - ArrowRadius; FSlateDrawElement::MakeBox( DrawElementsList, ArrowLayerID, FPaintGeometry(ArrowPoint, ArrowImage->ImageSize * ZoomFactor, ZoomFactor), ArrowImage, ESlateDrawEffect::None, Params.WireColor ); } } void FConnectionDrawingPolicy::DrawSplineWithArrow(const FGeometry& StartGeom, const FGeometry& EndGeom, const FConnectionParams& Params) { //@TODO: These values should be pushed into the Slate style, they are compensating for a bit of // empty space inside of the pin brush images. const float StartFudgeX = 4.0f; const float EndFudgeX = 4.0f; const FVector2D StartPoint = FGeometryHelper::VerticalMiddleRightOf(StartGeom) - FVector2D(StartFudgeX, 0.0f); const FVector2D EndPoint = FGeometryHelper::VerticalMiddleLeftOf(EndGeom) - FVector2D(ArrowRadius.X - EndFudgeX, 0); DrawSplineWithArrow(StartPoint, EndPoint, Params); } // Update the drawing policy with the set of hovered pins (which can be empty) void FConnectionDrawingPolicy::SetHoveredPins(const TSet< FEdGraphPinReference >& InHoveredPins, const TArray< TSharedPtr >& OverridePins, double HoverTime) { HoveredPins.Empty(); LastHoverTimeEvent = (OverridePins.Num() > 0) ? 0.0 : HoverTime; for (auto PinIt = OverridePins.CreateConstIterator(); PinIt; ++PinIt) { if (SGraphPin* GraphPin = PinIt->Get()) { HoveredPins.Add(GraphPin->GetPinObj()); } } // When we have only a single pin selected, we'll extend selection to apply the hover effect on the links const bool bMakeConnectedPinsHovered = (InHoveredPins.Num() == 1); // Convert the widget pointer for hovered pins to be EdGraphPin pointers for their connected nets (both ends of any connection) for (auto PinIt = InHoveredPins.CreateConstIterator(); PinIt; ++PinIt) { if (UEdGraphPin* Pin = PinIt->Get()) { if (Pin->LinkedTo.Num() > 0) { HoveredPins.Add(Pin); if (bMakeConnectedPinsHovered) { for (auto LinkIt = Pin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt) { HoveredPins.Add(*LinkIt); } } } } } } void FConnectionDrawingPolicy::SetMousePosition(const FVector2D& InMousePos) { LocalMousePosition = InMousePos; } void FConnectionDrawingPolicy::SetMarkedPin(TWeakPtr InMarkedPin) { if (InMarkedPin.IsValid()) { LastHoverTimeEvent = 0.0; TSharedPtr MarkedPinWidget = InMarkedPin.Pin(); if(UEdGraphPin* MarkedPin = MarkedPinWidget->GetPinObj()) { HoveredPins.Add(MarkedPin); for (auto LinkIt = MarkedPin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt) { HoveredPins.Add(*LinkIt); } } } } /** Util to make a 'distance->alpha' table and also return spline length */ float FConnectionDrawingPolicy::MakeSplineReparamTable(const FVector2D& P0, const FVector2D& P0Tangent, const FVector2D& P1, const FVector2D& P1Tangent, FInterpCurve& OutReparamTable) { const int32 NumSteps = 10; // TODO: Make this adaptive... OutReparamTable.Points.Empty(NumSteps); // Find range of input float Param = 0.f; const float MaxInput = 1.f; const float Interval = (MaxInput - Param)/((float)(NumSteps-1)); // Add first entry, using first point on curve, total distance will be 0 FVector2D OldSplinePos = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, Param); float TotalDist = 0.f; OutReparamTable.AddPoint(TotalDist, Param); Param += Interval; // Then work over rest of points for(int32 i=1; iComputeSplineTangent(Start, End); } void FConnectionDrawingPolicy::DrawConnection(int32 LayerId, const FVector2D& Start, const FVector2D& End, const FConnectionParams& Params) { const FVector2D& P0 = Start; const FVector2D& P1 = End; const FVector2D SplineTangent = ComputeSplineTangent(P0, P1); const FVector2D P0Tangent = (Params.StartTangent.IsNearlyZero()) ? ((Params.StartDirection == EGPD_Output) ? SplineTangent : -SplineTangent) : Params.StartTangent; const FVector2D P1Tangent = (Params.EndTangent.IsNearlyZero()) ? ((Params.EndDirection == EGPD_Input) ? SplineTangent : -SplineTangent) : Params.EndTangent; if (Settings->bTreatSplinesLikePins) { // Distance to consider as an overlap const float QueryDistanceTriggerThresholdSquared = FMath::Square(Settings->SplineHoverTolerance + Params.WireThickness * 0.5f); // Distance to pass the bounding box cull test. This is used for the bCloseToSpline output that can be used as a // dead zone to avoid mistakes caused by missing a double-click on a connection. const float QueryDistanceForCloseSquared = FMath::Square(FMath::Sqrt(QueryDistanceTriggerThresholdSquared) + Settings->SplineCloseTolerance); bool bCloseToSpline = false; { // The curve will include the endpoints but can extend out of a tight bounds because of the tangents // P0Tangent coefficient maximizes to 4/27 at a=1/3, and P1Tangent minimizes to -4/27 at a=2/3. const float MaximumTangentContribution = 4.0f / 27.0f; FBox2D Bounds(ForceInit); Bounds += FVector2D(P0); Bounds += FVector2D(P0 + MaximumTangentContribution * P0Tangent); Bounds += FVector2D(P1); Bounds += FVector2D(P1 - MaximumTangentContribution * P1Tangent); bCloseToSpline = Bounds.ComputeSquaredDistanceToPoint(LocalMousePosition) < QueryDistanceForCloseSquared; // Draw the bounding box for debugging #if 0 #define DrawSpaceLine(Point1, Point2, DebugWireColor) {const FVector2D FakeTangent = (Point2 - Point1).GetSafeNormal(); FSlateDrawElement::MakeDrawSpaceSpline(DrawElementsList, LayerId, Point1, FakeTangent, Point2, FakeTangent, ClippingRect, 1.0f, ESlateDrawEffect::None, DebugWireColor); } if (bCloseToSpline) { const FLinearColor BoundsWireColor = bCloseToSpline ? FLinearColor::Green : FLinearColor::White; FVector2D TL = Bounds.Min; FVector2D BR = Bounds.Max; FVector2D TR = FVector2D(Bounds.Max.X, Bounds.Min.Y); FVector2D BL = FVector2D(Bounds.Min.X, Bounds.Max.Y); DrawSpaceLine(TL, TR, BoundsWireColor); DrawSpaceLine(TR, BR, BoundsWireColor); DrawSpaceLine(BR, BL, BoundsWireColor); DrawSpaceLine(BL, TL, BoundsWireColor); } #endif } if (bCloseToSpline) { // Find the closest approach to the spline FVector2D ClosestPoint(ForceInit); float ClosestDistanceSquared = FLT_MAX; const int32 NumStepsToTest = 16; const float StepInterval = 1.0f / (float)NumStepsToTest; FVector2D Point1 = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, 0.0f); for (float TestAlpha = 0.0f; TestAlpha < 1.0f; TestAlpha += StepInterval) { const FVector2D Point2 = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, TestAlpha + StepInterval); const FVector2D ClosestPointToSegment = FMath::ClosestPointOnSegment2D(LocalMousePosition, Point1, Point2); const float DistanceSquared = (LocalMousePosition - ClosestPointToSegment).SizeSquared(); if (DistanceSquared < ClosestDistanceSquared) { ClosestDistanceSquared = DistanceSquared; ClosestPoint = ClosestPointToSegment; } Point1 = Point2; } // Record the overlap if (ClosestDistanceSquared < QueryDistanceTriggerThresholdSquared) { if (ClosestDistanceSquared < SplineOverlapResult.GetDistanceSquared()) { const float SquaredDistToPin1 = (Params.AssociatedPin1 != nullptr) ? (P0 - ClosestPoint).SizeSquared() : FLT_MAX; const float SquaredDistToPin2 = (Params.AssociatedPin2 != nullptr) ? (P1 - ClosestPoint).SizeSquared() : FLT_MAX; SplineOverlapResult = FGraphSplineOverlapResult(Params.AssociatedPin1, Params.AssociatedPin2, ClosestDistanceSquared, SquaredDistToPin1, SquaredDistToPin2, true); } } else if (ClosestDistanceSquared < QueryDistanceForCloseSquared) { SplineOverlapResult.SetCloseToSpline(true); } } } // Draw the spline itself FSlateDrawElement::MakeDrawSpaceSpline( DrawElementsList, LayerId, P0, P0Tangent, P1, P1Tangent, Params.WireThickness, ESlateDrawEffect::None, Params.WireColor ); if (Params.bDrawBubbles || (MidpointImage != nullptr)) { // This table maps distance along curve to alpha FInterpCurve SplineReparamTable; const float SplineLength = MakeSplineReparamTable(P0, P0Tangent, P1, P1Tangent, SplineReparamTable); // Draw bubbles on the spline if (Params.bDrawBubbles) { const float BubbleSpacing = 64.f * ZoomFactor; const float BubbleSpeed = 192.f * ZoomFactor; const FVector2D BubbleSize = BubbleImage->ImageSize * ZoomFactor * 0.2f * Params.WireThickness; float Time = (FPlatformTime::Seconds() - GStartTime); const float BubbleOffset = FMath::Fmod(Time * BubbleSpeed, BubbleSpacing); const int32 NumBubbles = FMath::CeilToInt(SplineLength/BubbleSpacing); for (int32 i = 0; i < NumBubbles; ++i) { const float Distance = ((float)i * BubbleSpacing) + BubbleOffset; if (Distance < SplineLength) { const float Alpha = SplineReparamTable.Eval(Distance, 0.f); FVector2D BubblePos = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, Alpha); BubblePos -= (BubbleSize * 0.5f); FSlateDrawElement::MakeBox( DrawElementsList, LayerId, FPaintGeometry( BubblePos, BubbleSize, ZoomFactor ), BubbleImage, ESlateDrawEffect::None, Params.WireColor ); } } } // Draw the midpoint image if (MidpointImage != nullptr) { // Determine the spline position for the midpoint const float MidpointAlpha = SplineReparamTable.Eval(SplineLength * 0.5f, 0.f); const FVector2D Midpoint = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha); // Approximate the slope at the midpoint (to orient the midpoint image to the spline) const FVector2D MidpointPlusE = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha + KINDA_SMALL_NUMBER); const FVector2D MidpointMinusE = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha - KINDA_SMALL_NUMBER); const FVector2D SlopeUnnormalized = MidpointPlusE - MidpointMinusE; // Draw the arrow const FVector2D MidpointDrawPos = Midpoint - MidpointRadius; const float AngleInRadians = SlopeUnnormalized.IsNearlyZero() ? 0.0f : FMath::Atan2(SlopeUnnormalized.Y, SlopeUnnormalized.X); FSlateDrawElement::MakeRotatedBox( DrawElementsList, LayerId, FPaintGeometry(MidpointDrawPos, MidpointImage->ImageSize * ZoomFactor, ZoomFactor), MidpointImage, ESlateDrawEffect::None, AngleInRadians, TOptional(), FSlateDrawElement::RelativeToElement, Params.WireColor ); } } } void FConnectionDrawingPolicy::DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2D& StartPoint, const FVector2D& EndPoint, UEdGraphPin* Pin) { FConnectionParams Params; DetermineWiringStyle(Pin, nullptr, /*inout*/ Params); DrawSplineWithArrow(StartPoint, EndPoint, Params); } void FConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) { Params.AssociatedPin1 = OutputPin; Params.AssociatedPin2 = InputPin; } void FConnectionDrawingPolicy::DetermineLinkGeometry( FArrangedChildren& ArrangedNodes, TSharedRef& OutputPinWidget, UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*out*/ FArrangedWidget*& StartWidgetGeometry, /*out*/ FArrangedWidget*& EndWidgetGeometry ) { StartWidgetGeometry = PinGeometries->Find(OutputPinWidget); if (TSharedPtr* pTargetWidget = PinToPinWidgetMap.Find(InputPin)) { TSharedRef InputWidget = (*pTargetWidget).ToSharedRef(); EndWidgetGeometry = PinGeometries->Find(InputWidget); } } void FConnectionDrawingPolicy::Draw(TMap, FArrangedWidget>& InPinGeometries, FArrangedChildren& ArrangedNodes) { PinGeometries = &InPinGeometries; BuildPinToPinWidgetMap(InPinGeometries); DrawPinGeometries(InPinGeometries, ArrangedNodes); } void FConnectionDrawingPolicy::BuildPinToPinWidgetMap(TMap, FArrangedWidget>& InPinGeometries) { PinToPinWidgetMap.Empty(); for (TMap, FArrangedWidget>::TIterator ConnectorIt(InPinGeometries); ConnectorIt; ++ConnectorIt) { TSharedRef SomePinWidget = ConnectorIt.Key(); SGraphPin& PinWidget = static_cast(SomePinWidget.Get()); PinToPinWidgetMap.Add(PinWidget.GetPinObj(), StaticCastSharedRef(SomePinWidget)); } } void FConnectionDrawingPolicy::DrawPinGeometries(TMap, FArrangedWidget>& InPinGeometries, FArrangedChildren& ArrangedNodes) { for (TMap, FArrangedWidget>::TIterator ConnectorIt(InPinGeometries); ConnectorIt; ++ConnectorIt) { TSharedRef SomePinWidget = ConnectorIt.Key(); SGraphPin& PinWidget = static_cast(SomePinWidget.Get()); UEdGraphPin* ThePin = PinWidget.GetPinObj(); if (ThePin && ThePin->Direction == EGPD_Output) { for (int32 LinkIndex=0; LinkIndex < ThePin->LinkedTo.Num(); ++LinkIndex) { FArrangedWidget* LinkStartWidgetGeometry = nullptr; FArrangedWidget* LinkEndWidgetGeometry = nullptr; UEdGraphPin* TargetPin = ThePin->LinkedTo[LinkIndex]; DetermineLinkGeometry(ArrangedNodes, SomePinWidget, ThePin, TargetPin, /*out*/ LinkStartWidgetGeometry, /*out*/ LinkEndWidgetGeometry); if (( LinkEndWidgetGeometry && LinkStartWidgetGeometry ) && !IsConnectionCulled( *LinkStartWidgetGeometry, *LinkEndWidgetGeometry )) { FConnectionParams Params; DetermineWiringStyle(ThePin, TargetPin, /*inout*/ Params); const TSharedPtr* ConnectedPinWidget = PinToPinWidgetMap.Find(TargetPin); if (ConnectedPinWidget && ConnectedPinWidget->IsValid()) { if ( PinWidget.AreConnectionsFaded() && (*ConnectedPinWidget)->AreConnectionsFaded() ) { Params.WireColor.A = 0.2f; } } DrawSplineWithArrow(LinkStartWidgetGeometry->Geometry, LinkEndWidgetGeometry->Geometry, Params); } } } } } bool FConnectionDrawingPolicy::IsConnectionCulled( const FArrangedWidget& StartLink, const FArrangedWidget& EndLink ) const { const float Top = FMath::Min( StartLink.Geometry.AbsolutePosition.Y, EndLink.Geometry.AbsolutePosition.Y ); const float Left = FMath::Min( StartLink.Geometry.AbsolutePosition.X, EndLink.Geometry.AbsolutePosition.X ); const float Bottom = FMath::Max( StartLink.Geometry.AbsolutePosition.Y + StartLink.Geometry.Size.Y, EndLink.Geometry.AbsolutePosition.Y + EndLink.Geometry.Size.Y ); const float Right = FMath::Max( StartLink.Geometry.AbsolutePosition.X + StartLink.Geometry.Size.X, EndLink.Geometry.AbsolutePosition.X + EndLink.Geometry.Size.X ); return Left > ClippingRect.Right || Right < ClippingRect.Left || Bottom < ClippingRect.Top || Top > ClippingRect.Bottom; } void FConnectionDrawingPolicy::SetIncompatiblePinDrawState(const TSharedPtr& StartPin, const TSet< TSharedRef >& VisiblePins) { } void FConnectionDrawingPolicy::ResetIncompatiblePinDrawState(const TSet< TSharedRef >& VisiblePins) { } void FConnectionDrawingPolicy::ApplyHoverDeemphasis(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor) { //@TODO: Move these parameters into the settings object const float FadeInBias = 0.75f; // Time in seconds before the fading starts to occur const float FadeInPeriod = 0.6f; // Time in seconds after the bias before the fade is fully complete const float TimeFraction = FMath::SmoothStep(0.0f, FadeInPeriod, (float)(FSlateApplication::Get().GetCurrentTime() - LastHoverTimeEvent - FadeInBias)); const float LightFraction = 0.25f; const FLinearColor DarkenedColor(0.0f, 0.0f, 0.0f, 0.5f); const FLinearColor LightenedColor(1.0f, 1.0f, 1.0f, 1.0f); const bool bContainsBoth = HoveredPins.Contains(InputPin) && HoveredPins.Contains(OutputPin); const bool bContainsOutput = HoveredPins.Contains(OutputPin); const bool bEmphasize = bContainsBoth || (bContainsOutput && (InputPin == nullptr)); if (bEmphasize) { Thickness = FMath::Lerp(Thickness, Thickness * ((Thickness < 2.5f) ? 3.5f : 2.5f), TimeFraction); WireColor = FMath::Lerp(WireColor, LightenedColor, LightFraction * TimeFraction); } else { WireColor = FMath::Lerp(WireColor, DarkenedColor, HoverDeemphasisDarkFraction * TimeFraction); } } ////////////////////////////////////////////////////////////////////////// // FGraphSplineOverlapResult void FGraphSplineOverlapResult::ComputeBestPin() { UEdGraphPin* BestPin = nullptr; if (Pin1 == nullptr) { BestPin = Pin2; } else if (Pin2 == nullptr) { BestPin = Pin1; } else { // Both are valid, now see if one of the pins has one connection while the other has more than one const int32 LinksTo1 = Pin1->LinkedTo.Num(); const int32 LinksTo2 = Pin2->LinkedTo.Num(); if ((LinksTo1 > 1) && (LinksTo2 == 1)) { BestPin = Pin2; } else if ((LinksTo1 == 1) && (LinksTo2 > 1)) { BestPin = Pin1; } else { // Both pins have multiple links, or both pins have one link, choose based on distance to the pins BestPin = (DistanceSquaredToPin1 < DistanceSquaredToPin2) ? Pin1 : Pin2; } } BestPinHandle = FGraphPinHandle(BestPin); Pin1 = nullptr; Pin2 = nullptr; } bool FGraphSplineOverlapResult::GetPins(const class SGraphPanel& InGraphPanel, UEdGraphPin*& OutPin1, UEdGraphPin*& OutPin2) const { OutPin1 = nullptr; OutPin2 = nullptr; if (IsValid()) { TSharedPtr Pin1Widget = Pin1Handle.FindInGraphPanel(InGraphPanel); TSharedPtr Pin2Widget = Pin2Handle.FindInGraphPanel(InGraphPanel); if (Pin1Widget.IsValid()) { OutPin1 = Pin1Widget->GetPinObj(); } if (Pin2Widget.IsValid()) { OutPin2 = Pin2Widget->GetPinObj(); } } return (OutPin1 != nullptr) && (OutPin2 != nullptr); } void FGraphSplineOverlapResult::GetPinWidgets(const class SGraphPanel& InGraphPanel, TSharedPtr& OutPin1, TSharedPtr& OutPin2) const { OutPin1 = nullptr; OutPin2 = nullptr; if (IsValid()) { OutPin1 = Pin1Handle.FindInGraphPanel(InGraphPanel); OutPin2 = Pin2Handle.FindInGraphPanel(InGraphPanel); } } TSharedPtr FConnectionDrawingPolicy::GetConnectionToolTip(const SGraphPanel& GraphPanel, const FGraphSplineOverlapResult& OverlapData) const { if (SGraphPin* BestPinFromHoveredSpline = OverlapData.GetBestPinWidget(GraphPanel).Get()) { return BestPinFromHoveredSpline->GetToolTip(); } return const_cast(GraphPanel).GetToolTip(); }