// Copyright Epic Games, Inc. All Rights Reserved. #include "MaterialEditor/MaterialNodes/SGraphNodeMaterialConvert.h" #include "ConnectionDrawingPolicy.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "MaterialGraph/MaterialGraphNode.h" #include "Materials/MaterialExpressionConvert.h" #include "Misc/Attribute.h" #include "ScopedTransaction.h" #include "Styling/AppStyle.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Layout/SSpacer.h" #include "Widgets/SBoxPanel.h" #define LOCTEXT_NAMESPACE "GraphNodeMaterialConvert" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // FConvertDragDropOp: Drag and Drop Operation used to form connections within the convert node //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// FConvertDragDropOp::FConvertDragDropOp(TSharedPtr InSourcePin) : SourcePin(InSourcePin) { Construct(); } void FConvertDragDropOp::OnDrop(bool bDropWasHandled, const FPointerEvent& MouseEvent) { FDragDropOperation::OnDrop(bDropWasHandled, MouseEvent); if (SourcePin) { SourcePin->CancelDragDrop(); } } void FConvertDragDropOp::OnDragged(const class FDragDropEvent& DragDropEvent) { FDragDropOperation::OnDragged(DragDropEvent); ScreenPosition = DragDropEvent.GetScreenSpacePosition(); } TSharedPtr FConvertDragDropOp::GetSourcePin() const { return SourcePin; } UE::Slate::FDeprecateVector2DResult FConvertDragDropOp::GetScreenPosition() const { return ScreenPosition; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SMaterialExpressionConvertGraphPin: The Outer pins that form connections to other material graph nodes //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SMaterialExpressionConvertGraphPin::Construct(const FArguments& InArgs, UEdGraphPin* InPin) { SGraphPin::Construct(SGraphPin::FArguments(), InPin); // Setup custom hover logic to only treat this pin as hovered when you hover over the main pin image this->SetHover(TAttribute::CreateSP(this, &SMaterialExpressionConvertGraphPin::IsHoveredOverPrimaryPin)); } void SMaterialExpressionConvertGraphPin::CreateInnerPins(TSharedRef InOwningGraphNodeWidget) { UMaterialGraphNode* MaterialNode = InOwningGraphNodeWidget->GetMaterialGraphNode(); if (!MaterialNode) { return; } UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return; } UEdGraphPin* EdGraphPin = GetPinObj(); if (!EdGraphPin) { return; } if (TSharedPtr PinnedRowWidget = GetFullPinHorizontalRowWidget().Pin()) { const bool bIsInputPin = EdGraphPin->Direction == EGPD_Input; const int32 PinIndex = EdGraphPin->SourceIndex; const EMaterialExpressionConvertType ConvertType = bIsInputPin ? ConvertExpression->ConvertInputs[PinIndex].Type : ConvertExpression->ConvertOutputs[PinIndex].Type; const int32 PinComponentCount = MaterialExpressionConvertType::GetComponentCount(ConvertType); InnerPins.Reset(PinComponentCount); TSharedPtr InnerPinsVerticalBox = SNew(SVerticalBox); for (int32 ComponentIndex = 0; ComponentIndex < PinComponentCount; ++ComponentIndex) { TSharedRef NewInnerPin = SNew(SConvertInnerPin, InOwningGraphNodeWidget, SharedThis(this), bIsInputPin, PinIndex, ComponentIndex ); InnerPinsVerticalBox->AddSlot() .HAlign(bIsInputPin ? HAlign_Right : HAlign_Left) [ NewInnerPin ]; InnerPins.Add(NewInnerPin); } // Insert as last element (INDEX_NONE) for input pins, first element for output pins PinnedRowWidget->InsertSlot(bIsInputPin ? INDEX_NONE : 0) .HAlign(bIsInputPin ? HAlign_Right : HAlign_Left) .VAlign(VAlign_Center) .AutoWidth() [ InnerPinsVerticalBox.ToSharedRef() ]; } } bool SMaterialExpressionConvertGraphPin::IsHoveredOverPrimaryPin() const { TSharedPtr PinImageWidget = this->GetPinImageWidget(); return PinImageWidget ? PinImageWidget->IsHovered() : false; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SConvertInnerPin: The inner pins used to route values with the convert node //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SConvertInnerPin::Construct( const FArguments& InArgs, TSharedPtr InOwningNode, TSharedPtr InOwningPin, bool bInIsInputPin, int32 InPinIndex, int32 InComponentIndex ) { WeakOwningNode = InOwningNode; WeakOwningPin = InOwningPin; bIsInputPin = bInIsInputPin; PinIndex = InPinIndex; ComponentIndex = InComponentIndex; // Construct Horizontal Box and add the PinImage to it TSharedPtr HorizontalBox = SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(2.5f, 0.0f) [ SAssignNew(PinImage, SImage) .Image(this, &SConvertInnerPin::GetPinBrush) ]; // Add default value entry to either the front or back of the hbox depending on if we're an input pin HorizontalBox->InsertSlot(bIsInputPin ? 0 : INDEX_NONE) .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(2.5f, 0.0f) [ SNew(STextBlock) .TextStyle(FAppStyle::Get(), "MonospacedText") .Visibility(this, &SConvertInnerPin::GetPinNameVisibility) .Text(this, &SConvertInnerPin::GetPinName) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(2.5f, 0.0f) [ SNew(SNumericEntryBox) .EditableTextBoxStyle(FAppStyle::Get(), "Graph.EditableTextBox") .BorderForegroundColor(FSlateColor::UseForeground()) .Visibility(this, &SConvertInnerPin::GetDefaultValueVisibility) .Value(this, &SConvertInnerPin::GetDefaultValue) .OnValueCommitted(this, &SConvertInnerPin::SetDefaultValue) ] ]; // Add inner node connection space to either the front or back of the hbox depending on if we're an input pin HorizontalBox->InsertSlot(bIsInputPin ? INDEX_NONE : 0) .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SSpacer) .Size(FVector2D(30, 0)) ]; ChildSlot [ HorizontalBox.ToSharedRef() ]; } int32 SConvertInnerPin::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { LayerId = SCompoundWidget::OnPaint( Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled ); // Update CenterAbsolute after we've painted our PinImage if (PinImage.IsValid()) { CenterAbsolute = FGeometryHelper::CenterOf(PinImage->GetPaintSpaceGeometry()); } return LayerId; } FReply SConvertInnerPin::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { if (MouseEvent.IsAltDown()) { BreakConnections(); return FReply::Handled(); } return FReply::Handled() .DetectDrag(SharedThis(this), MouseEvent.GetEffectingButton()) .CaptureMouse(SharedThis(this)); } // We still want to detect right-clicks on mouse button up return FReply::Handled(); } FReply SConvertInnerPin::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton) { // Only show to context menu if we right click the actual InnerPin if (PinImage && PinImage->GetCachedGeometry().IsUnderLocation(MouseEvent.GetScreenSpacePosition())) { FMenuBuilder ContextMenuBuilder(true, NULL); const FText BreakConnectionText = WeakConnectedPins.Num() == 1 ? LOCTEXT("ContextMenu.BreakInnerConnection", "Break Connection") : LOCTEXT("ContextMenu.BreakInnerConnections", "Break Connections"); ContextMenuBuilder.AddMenuEntry( BreakConnectionText, FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SConvertInnerPin::BreakConnections)) ); FSlateApplication::Get().PushMenu( AsShared(), FWidgetPath(), ContextMenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu) ); return FReply::Handled(); } } return FReply::Unhandled(); } FReply SConvertInnerPin::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (TSharedPtr OwningNode = WeakOwningNode.Pin()) { TSharedRef DragDropOp = MakeShareable(new FConvertDragDropOp(SharedThis(this))); OwningNode->SetCurrentDragDropOp(DragDropOp); return FReply::Handled().BeginDragDrop(DragDropOp); } return FReply::Unhandled(); } FReply SConvertInnerPin::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) { if (TSharedPtr DragDropOp = DragDropEvent.GetOperationAs()) { if (TSharedPtr SourcePin = DragDropOp->GetSourcePin()) { // Is this a valid connection if (SourcePin.Get() != this && SourcePin->WeakOwningNode == this->WeakOwningNode && SourcePin->bIsInputPin != this->bIsInputPin) { if (TSharedPtr OwningNode = WeakOwningNode.Pin()) { // Form connection between two pins OwningNode->FormConnection(SourcePin, SharedThis(this)); // Clear The Current Drag Drop Op OwningNode->SetCurrentDragDropOp(nullptr); return FReply::Handled(); } } } } return FReply::Unhandled(); } const FSlateBrush* SConvertInnerPin::GetPinBrush() const { const bool bHasConnections = WeakConnectedPins.Num() > 0; const FSlateBrush* ConnectedBrush = FAppStyle::GetBrush("Graph.Pin.Connected"); const FSlateBrush* DisconnectedBrush = FAppStyle::GetBrush("Graph.Pin.Disconnected"); return bHasConnections ? ConnectedBrush : DisconnectedBrush; } void SConvertInnerPin::CancelDragDrop() { if (TSharedPtr OwningNode = WeakOwningNode.Pin()) { OwningNode->SetCurrentDragDropOp(nullptr); } } UE::Slate::FDeprecateVector2DResult SConvertInnerPin::GetPinCenterAbsolute() const { return CenterAbsolute; } void SConvertInnerPin::AddConnection(TSharedPtr InOtherPin) { WeakConnectedPins.Add(InOtherPin); } void SConvertInnerPin::RemoveConnection(TSharedPtr InOtherPin) { WeakConnectedPins.Remove(InOtherPin); } void SConvertInnerPin::RemoveAllConnections() { for (TWeakPtr WeakConnectedPin : WeakConnectedPins) { if (TSharedPtr ConnectedPin = WeakConnectedPin.Pin()) { ConnectedPin->RemoveConnection(SharedThis(this)); } } WeakConnectedPins.Reset(); } void SConvertInnerPin::BreakConnections() { if (TSharedPtr OwningNode = WeakOwningNode.Pin()) { OwningNode->BreakConnections(SharedThis(this)); } } EVisibility SConvertInnerPin::GetDefaultValueVisibility() const { bool bIsVisible = false; if (bIsInputPin) { if (TSharedPtr OwningPin = WeakOwningPin.Pin()) { // Only show Input Default values if the owning SMaterialExpressionConvertGraphPin isn't connected bIsVisible = !OwningPin->IsConnected(); } } else { // Show Output pin default values if they don't yet have a connection to an input inner pin bIsVisible = WeakConnectedPins.IsEmpty(); } return bIsVisible ? EVisibility::Visible : EVisibility::Collapsed; } TOptional SConvertInnerPin::GetDefaultValue() const { TSharedPtr OwningNode = WeakOwningNode.Pin(); if (!OwningNode) { return TOptional(); } return OwningNode->GetDefaultValue(SharedThis(this)); } void SConvertInnerPin::SetDefaultValue(float InDefaultValue, ETextCommit::Type CommitType) { if (TSharedPtr OwningNode = WeakOwningNode.Pin()) { OwningNode->SetDefaultValue( SharedThis(this), InDefaultValue ); } } EVisibility SConvertInnerPin::GetPinNameVisibility() const { TSharedPtr OwningPin = WeakOwningPin.Pin(); if (OwningPin && OwningPin->GetInnerPins().Num() == 1) { return EVisibility::Collapsed; } return EVisibility::SelfHitTestInvisible; } FText SConvertInnerPin::GetPinName() const { TSharedPtr OwningPin = WeakOwningPin.Pin(); if (OwningPin && OwningPin->GetInnerPins().Num() == 1) { return INVTEXT(""); } switch (ComponentIndex) { case 0: return INVTEXT("R"); case 1: return INVTEXT("G"); case 2: return INVTEXT("B"); case 3: return INVTEXT("A"); default: return INVTEXT("_"); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SGraphNodeMaterialConvert: Custom Slate widget for UMaterialExpressionConvert //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SGraphNodeMaterialConvert::Construct(const FArguments& InArgs, class UMaterialGraphNode* InNode) { this->GraphNode = InNode; this->MaterialNode = InNode; this->UpdateGraphNode(); } void SGraphNodeMaterialConvert::CreatePinWidgets() { SGraphNodeMaterialBase::CreatePinWidgets(); if (!MaterialNode) { return; } UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return; } // After we've set up all of our pins, set up initial connections based on our ConvertMappings for (const FMaterialExpressionConvertMapping& ConvertMapping : ConvertExpression->ConvertMappings) { TSharedPtr InputInnerPin = GetInnerPin(true, ConvertMapping.InputIndex, ConvertMapping.InputComponentIndex); TSharedPtr OutputInnerPin = GetInnerPin(false, ConvertMapping.OutputIndex, ConvertMapping.OutputComponentIndex); if (InputInnerPin && OutputInnerPin) { InputInnerPin->AddConnection(OutputInnerPin); OutputInnerPin->AddConnection(InputInnerPin); } } } TSharedPtr SGraphNodeMaterialConvert::CreatePinWidget(UEdGraphPin* Pin) const { return SNew(SMaterialExpressionConvertGraphPin, Pin); } void SGraphNodeMaterialConvert::AddPin(const TSharedRef& PinToAdd) { SGraphNodeMaterialBase::AddPin(PinToAdd); // Create our Inner Pins, which are used to route input values to output values TSharedRef ConvertOuterPin = StaticCastSharedRef(PinToAdd); ConvertOuterPin->CreateInnerPins(SharedThis(this)); } void SGraphNodeMaterialConvert::CreateInputSideAddButton(TSharedPtr InputBox) { TSharedRef AddPinButton = StaticCastSharedRef(AddPinButtonContent( LOCTEXT("GraphNodeMaterialConvert_AddInputPin", "Add Input"), LOCTEXT("GraphNodeMaterialConvert_AddInputPin_Tooltip", "Add an input to this convert node."), false /* bRightSide */ )); AddPinButton->SetOnClicked(FOnClicked::CreateSP(this, &SGraphNodeMaterialConvert::OnAddInputPinClicked)); InputBox->AddSlot() .FillHeight(1.f) .Padding(5.f) .VAlign(VAlign_Bottom) .HAlign(HAlign_Left) [ AddPinButton ]; } void SGraphNodeMaterialConvert::CreateOutputSideAddButton(TSharedPtr OutputBox) { TSharedRef AddPinButton = StaticCastSharedRef(AddPinButtonContent( LOCTEXT("GraphNodeMaterialConvert_AddOutputPin", "Add Output"), LOCTEXT("GraphNodeMaterialConvert_AddOutputPin_Tooltip", "Add an output to this convert node."), true /* bRightSide */ )); AddPinButton->SetOnClicked(FOnClicked::CreateSP(this, &SGraphNodeMaterialConvert::OnAddOutputPinClicked)); OutputBox->AddSlot() .FillHeight(1.f) .Padding(5.f) .VAlign(VAlign_Bottom) .HAlign(HAlign_Right) [ AddPinButton ]; } EVisibility SGraphNodeMaterialConvert::IsAddPinButtonVisible() const { return EVisibility::Visible; } FReply SGraphNodeMaterialConvert::OnAddInputPinClicked() { FSlateApplication::Get().PushMenu( AsShared(), FWidgetPath(), CreateAddPinContextMenu(/* bInputPin = */ true), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu) ); return FReply::Handled(); } FReply SGraphNodeMaterialConvert::OnAddOutputPinClicked() { FSlateApplication::Get().PushMenu( AsShared(), FWidgetPath(), CreateAddPinContextMenu(/* bInputPin = */ false), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu) ); return FReply::Handled(); } TSharedRef SGraphNodeMaterialConvert::CreateAddPinContextMenu(bool bInputPin) { FMenuBuilder ContextMenuBuilder(true, NULL); static const EMaterialExpressionConvertType ConvertTypes[] = { EMaterialExpressionConvertType::Scalar, EMaterialExpressionConvertType::Vector2, EMaterialExpressionConvertType::Vector3, EMaterialExpressionConvertType::Vector4 }; for (const EMaterialExpressionConvertType ConvertType : ConvertTypes) { ContextMenuBuilder.AddMenuEntry( MaterialExpressionConvertType::ToText(ConvertType), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP( this, &SGraphNodeMaterialConvert::AddNewPin, bInputPin, ConvertType )) ); } return ContextMenuBuilder.MakeWidget(); } void SGraphNodeMaterialConvert::AddNewPin(bool bInputPin, EMaterialExpressionConvertType ConvertType) { UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return; } const FScopedTransaction Transaction(LOCTEXT("AddPin", "Add Pin")); ConvertExpression->Modify(); if (bInputPin) { FMaterialExpressionConvertInput NewInput; NewInput.Type = ConvertType; ConvertExpression->ConvertInputs.Add(NewInput); } else { FMaterialExpressionConvertOutput NewOutput; NewOutput.Type = ConvertType; ConvertExpression->ConvertOutputs.Add(NewOutput); } MaterialNode->ReconstructNode(); } int32 SGraphNodeMaterialConvert::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { LayerId = SGraphNodeMaterialBase::OnPaint( Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled ); // Only Draw Connections when we're at a high-enough detail. Early-out otherwise if (GetCurrentLOD() <= EGraphRenderingLOD::LowDetail) { return LayerId; } if (!MaterialNode) { return LayerId; } UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return LayerId; } for (const FMaterialExpressionConvertMapping& ConvertMapping : ConvertExpression->ConvertMappings) { TSharedPtr InputInnerPin = GetInnerPin(true, ConvertMapping.InputIndex, ConvertMapping.InputComponentIndex); TSharedPtr OutputInnerPin = GetInnerPin(false, ConvertMapping.OutputIndex, ConvertMapping.OutputComponentIndex); if (InputInnerPin && OutputInnerPin) { const FVector2f CurveStart = InputInnerPin->GetPinCenterAbsolute(); const FVector2f CurveEnd = OutputInnerPin->GetPinCenterAbsolute(); MakeConnectionCurve(AllottedGeometry, OutDrawElements, LayerId, CurveStart, CurveEnd); } } if (CurrentDragDropOp.IsValid()) { if (TSharedPtr SourcePin = CurrentDragDropOp->GetSourcePin()) { const FVector2f CurveStart = SourcePin->GetPinCenterAbsolute(); const FVector2f CurveEnd = CurrentDragDropOp->GetScreenPosition() + Inverse(Args.GetWindowToDesktopTransform()); MakeConnectionCurve(AllottedGeometry, OutDrawElements, LayerId, CurveStart, CurveEnd); } } return LayerId; } void SGraphNodeMaterialConvert::FormConnection(TSharedPtr InnerPinA, TSharedPtr InnerPinB) { check(InnerPinA->IsInputPin() != InnerPinB->IsInputPin()); if (!MaterialNode) { return; } UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return; } const FScopedTransaction Transaction(LOCTEXT("AddConnection", "Convert Node: Add Connection")); ConvertExpression->Modify(); // Determine Which Pin is the input and which is the output TSharedPtr InputPin = InnerPinA->IsInputPin() ? InnerPinA : InnerPinB; TSharedPtr OutputPin = InnerPinA->IsInputPin() ? InnerPinB : InnerPinA; FMaterialExpressionConvertMapping NewConvertMapping; NewConvertMapping.InputIndex = InputPin->GetPinIndex(); NewConvertMapping.InputComponentIndex = InputPin->GetComponentIndex(); NewConvertMapping.OutputIndex = OutputPin->GetPinIndex(); NewConvertMapping.OutputComponentIndex = OutputPin->GetComponentIndex(); // Remove any mappings with the same OutputIndex and OutputComponentIndex ConvertExpression->ConvertMappings.RemoveAll( [&NewConvertMapping](const FMaterialExpressionConvertMapping& ExistingConvertMapping) { return ExistingConvertMapping.OutputIndex == NewConvertMapping.OutputIndex && ExistingConvertMapping.OutputComponentIndex == NewConvertMapping.OutputComponentIndex; } ); // Add new convert mapping and refresh ConvertExpression->ConvertMappings.Add(NewConvertMapping); ConvertExpression->RefreshNode(); // Input Pins can have multiple connections InputPin->AddConnection(OutputPin); // Output Pins can only have one connection, so clean up any existing connections OutputPin->RemoveAllConnections(); OutputPin->AddConnection(InputPin); } void SGraphNodeMaterialConvert::BreakConnections(TSharedPtr InnerPin) { check(InnerPin); if (!MaterialNode) { return; } UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return; } const FScopedTransaction Transaction(LOCTEXT("BreakConnections", "Convert Node: Break Connections")); ConvertExpression->Modify(); const bool bIsInputPin = InnerPin->IsInputPin(); const int32 PinIndex = InnerPin->GetPinIndex(); const int32 ComponentIndex = InnerPin->GetComponentIndex(); ConvertExpression->ConvertMappings.RemoveAll( [bIsInputPin, PinIndex, ComponentIndex](const FMaterialExpressionConvertMapping& ExistingConvertMapping) { if (bIsInputPin) { return ExistingConvertMapping.InputIndex == PinIndex && ExistingConvertMapping.InputComponentIndex == ComponentIndex; } else { return ExistingConvertMapping.OutputIndex == PinIndex && ExistingConvertMapping.OutputComponentIndex == ComponentIndex; } } ); ConvertExpression->RefreshNode(); InnerPin->RemoveAllConnections(); } TOptional SGraphNodeMaterialConvert::GetDefaultValue(TSharedPtr InnerPin) const { UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return TOptional(); } const int32 PinIndex = InnerPin->GetPinIndex(); const int32 ComponentIndex = InnerPin->GetComponentIndex(); if (InnerPin->IsInputPin()) { return ConvertExpression->ConvertInputs[PinIndex].DefaultValue.Component(ComponentIndex); } else { return ConvertExpression->ConvertOutputs[PinIndex].DefaultValue.Component(ComponentIndex); } } void SGraphNodeMaterialConvert::SetDefaultValue(TSharedPtr InnerPin, const float InDefaultValue) { UMaterialExpressionConvert* ConvertExpression = Cast(MaterialNode->MaterialExpression); if (!ConvertExpression) { return; } const FScopedTransaction Transaction( LOCTEXT( "SetPinDefaultValue", "Set Pin Default Value" ) ); ConvertExpression->Modify(); const int32 PinIndex = InnerPin->GetPinIndex(); const int32 ComponentIndex = InnerPin->GetComponentIndex(); if (InnerPin->IsInputPin()) { ConvertExpression->ConvertInputs[PinIndex].DefaultValue.Component(ComponentIndex) = InDefaultValue; } else { ConvertExpression->ConvertOutputs[PinIndex].DefaultValue.Component(ComponentIndex) = InDefaultValue; } ConvertExpression->RefreshNode(); } TSharedPtr SGraphNodeMaterialConvert::GetInnerPin(bool bInputPin, const int32 InPinIndex, const int32 InComponentIndex) const { // First Find relevant input/output graph pin TSharedPtr GraphPin = nullptr; if (bInputPin) { if (InputPins.IsValidIndex(InPinIndex)) { GraphPin = StaticCastSharedRef(InputPins[InPinIndex]); } } else { if (OutputPins.IsValidIndex(InPinIndex)) { GraphPin = StaticCastSharedRef(OutputPins[InPinIndex]); } } // Then get that the relevant inner pin from that graph pin if (GraphPin.IsValid() && GraphPin->GetInnerPins().IsValidIndex(InComponentIndex)) { return GraphPin->GetInnerPins()[InComponentIndex]; } // No valid inner pin found, return nullptr return nullptr; } void SGraphNodeMaterialConvert::MakeConnectionCurve( const FGeometry& InAllottedGeometry, FSlateWindowElementList& OutDrawElements, int32& InOutLayerId, const FVector2f& InCurveStart, const FVector2f& InCurveEnd ) const { FVector2f LocalStart = InAllottedGeometry.AbsoluteToLocal(InCurveStart); FVector2f LocalEnd = InAllottedGeometry.AbsoluteToLocal(InCurveEnd); //If LocalStart.X is > LocalEnd.X, swap values if (LocalStart.X > LocalEnd.X) { std::swap(LocalStart, LocalEnd); } const bool bAllValuesValid = (LocalStart.X != -FLT_MAX) && (LocalStart.Y != -FLT_MAX) && (LocalEnd.X != -FLT_MAX) && (LocalEnd.Y != -FLT_MAX); if (bAllValuesValid) { /* Because we make sure the left-most position is always the start position, * curve direction is always pointing to the right */ static const FVector2f Direction = FVector2f(100.0f, 0.0f); FSlateDrawElement::MakeSpline( OutDrawElements, ++InOutLayerId, InAllottedGeometry.ToPaintGeometry(), LocalStart, Direction, LocalEnd, Direction, 2 ); } } #undef LOCTEXT_NAMESPACE