Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp
luc rosenzweig c9eb19e6cd This changelist contains both a refactor of the material graph system, as well as an improved custom HLSL node.
The first series of changes relate to modifying the way that SGraphNode* inheritants are created. Previously, NodeFactory was responsible of creating the SGraphNode* inheritants based on some runtime type checking (which basically means a series of if statements checking if the passed in pointer is of a certain child class type). Now, the UMaterialGraph* inheritants (the controller classes in the MVC paradigm of the material graph) are responsible for creating their own UI components (SGraphNode* inheritants). This just means that we now polymorphically create the proper SGraphNode* for the given UMaterialGraph* inheritant.

This refactor allows us to more flexibly create a variety of nodes types instead of relying on SGraphNodeMaterialBase as a one size fits all solution, leading to the new HLSL custom node. Previously, this node didn't support showing the syntax-highlighted code inline in the node itself. Because we now use polymorphism to create the nodes, it was very easy to create new UMaterialGraph* and SGraphNode* inheritants to support this new change.

Other changes which relate to propagating changes (to affect previews) in MaterialGraphNode.cpp are needed because there previously wasn't the mechanism to do so (which may have been a bug). For instance, suppose you had a constant vector3 feeding into a custom HLSL node which just outputs the color coming from that constant vector3 node. It would be obvious that changing the vector3, requires a preview update of the HLSL node. However, that wouldn't happen - the preview on the HLSL node just stayed the same. These changes addresses that issue.

There is one last series of changes which relates to collapsing the HLSL code in the node. We want to make sure that this change doesn't mess up the layouts of artists' already made material graphs which use the custom HLSL node. In order to address this, we added the ability to collapse the code in order to hide it. Furthermore, we had to make sure that projects which previously used a custom HLSL node, have the code collapsed by default (because it wasn't there before). However, creating a new HLSL node once this change comes in, would yield an uncollapsed node. The collapsing of nodes uses the "Advanced Pins" chevron/collapser which doesn't save its state after saving and exiting the material. It was crucial, in order to preserve the layout of the material graph, that the saving happens. However, previously, the only way to save state, was to go through a recompile/regeneration of the previews. Therefore, it was necessary to add a special function in the material editor utilities which would just mark the material as dirty such that states like whether the code was collapsed save throughout sessions.

#jira UE-146779
#rb jason.nadro

[CL 26677757 by luc rosenzweig in ue5-main branch]
2023-07-28 13:01:00 -04:00

683 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MaterialNodes/SGraphNodeMaterialBase.h"
#include "CanvasItem.h"
#include "CanvasTypes.h"
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "CoreGlobals.h"
#include "Delegates/Delegate.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Engine/World.h"
#include "GenericPlatform/ICursor.h"
#include "GraphEditor.h"
#include "GraphEditorSettings.h"
#include "HAL/PlatformCrt.h"
#include "Layout/Geometry.h"
#include "Layout/Margin.h"
#include "Layout/SlateRect.h"
#include "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionBreakMaterialAttributes.h"
#include "Materials/MaterialExpressionMakeMaterialAttributes.h"
#include "Materials/MaterialExpressionStrata.h"
#include "Materials/MaterialFunction.h"
#include "Math/Color.h"
#include "Math/IntPoint.h"
#include "Math/IntRect.h"
#include "Misc/App.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/Guid.h"
#include "Misc/Optional.h"
#include "RHI.h"
#include "RHICommandList.h"
#include "Rendering/DrawElements.h"
#include "Rendering/SlateRenderer.h"
#include "RenderingThread.h"
#include "SGraphPanel.h"
#include "SGraphPin.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Templates/Casts.h"
#include "TutorialMetaData.h"
#include "Types/SlateEnums.h"
#include "Types/SlateStructs.h"
#include "UObject/NameTypes.h"
#include "UObject/ObjectPtr.h"
#include "UnrealClient.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SOverlay.h"
#include "Widgets/SViewport.h"
#include "Widgets/Text/STextBlock.h"
#include "Rendering/StrataMaterialShared.h"
#include "SGraphSubstrateMaterial.h"
#include "MaterialShared.h"
class FWidgetStyle;
class SWidget;
struct FSlateBrush;
static const FName NAME_Pin_NotConnectable("Graph.Pin.Dummy");
static const FSlateBrush* CacheImg_Pin_NotConnectable = nullptr;
/**
* Simple representation of the backbuffer that the preview canvas renders to
* This class may only be accessed from the render thread
*/
class FSlateMaterialPreviewRenderTarget : public FRenderTarget
{
public:
/** FRenderTarget interface */
virtual FIntPoint GetSizeXY() const
{
return ClippingRect.Size();
}
/** Sets the texture that this target renders to */
void SetRenderTargetTexture( FTexture2DRHIRef& InRHIRef )
{
RenderTargetTextureRHI = InRHIRef;
}
/** Clears the render target texture */
void ClearRenderTargetTexture()
{
RenderTargetTextureRHI.SafeRelease();
}
/** Sets the viewport rect for the render target */
void SetViewRect( const FIntRect& InViewRect )
{
ViewRect = InViewRect;
}
/** Gets the viewport rect for the render target */
const FIntRect& GetViewRect() const
{
return ViewRect;
}
/** Sets the clipping rect for the render target */
void SetClippingRect( const FIntRect& InClippingRect )
{
ClippingRect = InClippingRect;
}
/** Gets the clipping rect for the render target */
const FIntRect& GetClippingRect() const
{
return ClippingRect;
}
private:
FIntRect ViewRect;
FIntRect ClippingRect;
};
/*-----------------------------------------------------------------------------
FPreviewViewport
-----------------------------------------------------------------------------*/
FPreviewViewport::FPreviewViewport(class UMaterialGraphNode* InNode)
: MaterialNode(InNode)
, PreviewElement( new FPreviewElement )
{
if (MaterialNode)
{
MaterialNode->InvalidatePreviewMaterialDelegate.BindRaw(this, &FPreviewViewport::UpdatePreviewNodeRenderProxy);
}
}
FPreviewViewport::~FPreviewViewport()
{
if (MaterialNode)
{
MaterialNode->InvalidatePreviewMaterialDelegate.Unbind();
}
// Pass the preview element to the render thread so that it's deleted after it's shown for the last time
ENQUEUE_RENDER_COMMAND(SafeDeletePreviewElement)(
[PreviewElement = PreviewElement](FRHICommandListImmediate& RHICmdList) mutable
{
PreviewElement.Reset();
}
);
}
void FPreviewViewport::OnDrawViewport( const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, class FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled )
{
FSlateRect SlateCanvasRect = AllottedGeometry.GetLayoutBoundingRect();
FSlateRect ClippedCanvasRect = SlateCanvasRect.IntersectionWith(MyCullingRect);
FIntRect CanvasRect(
FMath::TruncToInt( FMath::Max(0.0f, SlateCanvasRect.Left) ),
FMath::TruncToInt( FMath::Max(0.0f, SlateCanvasRect.Top) ),
FMath::TruncToInt( FMath::Max(0.0f, SlateCanvasRect.Right) ),
FMath::TruncToInt( FMath::Max(0.0f, SlateCanvasRect.Bottom) ) );
FIntRect ClippingRect(
FMath::TruncToInt( FMath::Max(0.0f, ClippedCanvasRect.Left) ),
FMath::TruncToInt( FMath::Max(0.0f, ClippedCanvasRect.Top) ),
FMath::TruncToInt( FMath::Max(0.0f, ClippedCanvasRect.Right) ),
FMath::TruncToInt( FMath::Max(0.0f, ClippedCanvasRect.Bottom) ) );
bool bIsRealtime = MaterialNode->RealtimeDelegate.IsBound() ? MaterialNode->RealtimeDelegate.Execute() : false;
if (PreviewElement->BeginRenderingCanvas(CanvasRect, ClippingRect, MaterialNode, bIsRealtime))
{
// Draw above everything else
uint32 PreviewLayer = LayerId+1;
FSlateDrawElement::MakeCustom( OutDrawElements, PreviewLayer, PreviewElement );
}
}
FIntPoint FPreviewViewport::GetSize() const
{
return FIntPoint(96,96);
}
void FPreviewViewport::UpdatePreviewNodeRenderProxy()
{
if (PreviewElement.IsValid())
{
PreviewElement->UpdateExpressionPreview(MaterialNode);
}
}
/////////////////////////////////////////////////////
// FPreviewElement
FPreviewElement::FPreviewElement()
: RenderTarget(new FSlateMaterialPreviewRenderTarget)
, ExpressionPreview(nullptr)
, bIsRealtime(false)
{
}
FPreviewElement::~FPreviewElement()
{
delete RenderTarget;
}
bool FPreviewElement::BeginRenderingCanvas( const FIntRect& InCanvasRect, const FIntRect& InClippingRect, UMaterialGraphNode* InGraphNode, bool bInIsRealtime )
{
if(InCanvasRect.Size().X > 0 && InCanvasRect.Size().Y > 0 && InClippingRect.Size().X > 0 && InClippingRect.Size().Y > 0 && InGraphNode != NULL)
{
/**
* Struct to contain all info that needs to be passed to the render thread
*/
struct FPreviewRenderInfo
{
/** Size of the Canvas tile */
FIntRect CanvasRect;
/** How to clip the canvas tile */
FIntRect ClippingRect;
/** Render proxy for the expression preview */
FMaterialRenderProxy* RenderProxy;
/** Whether preview is using realtime values */
bool bIsRealtime;
};
FPreviewRenderInfo RenderInfo;
RenderInfo.CanvasRect = InCanvasRect;
RenderInfo.ClippingRect = InClippingRect;
RenderInfo.RenderProxy = InGraphNode->GetExpressionPreview();
RenderInfo.bIsRealtime = bInIsRealtime;
FPreviewElement* PreviewElement = this;
ENQUEUE_RENDER_COMMAND(BeginRenderingPreviewCanvas)(
[PreviewElement, RenderInfo](FRHICommandListImmediate& RHICmdList)
{
PreviewElement->RenderTarget->SetViewRect(RenderInfo.CanvasRect);
PreviewElement->RenderTarget->SetClippingRect(RenderInfo.ClippingRect);
PreviewElement->ExpressionPreview = RenderInfo.RenderProxy;
PreviewElement->bIsRealtime = RenderInfo.bIsRealtime;
}
);
return true;
}
return false;
}
void FPreviewElement::UpdateExpressionPreview(UMaterialGraphNode* MaterialNode)
{
FPreviewElement* PreviewElement = this;
FMaterialRenderProxy* InRenderProxy = MaterialNode ? MaterialNode->GetExpressionPreview() : nullptr;
ENQUEUE_RENDER_COMMAND(UpdatePreviewNodeRenderProxy)(
[PreviewElement, InRenderProxy](FRHICommandListImmediate& RHICmdList)
{
PreviewElement->ExpressionPreview = InRenderProxy;
}
);
}
void FPreviewElement::DrawRenderThread(FRHICommandListImmediate& RHICmdList, const void* InWindowBackBuffer)
{
if(ExpressionPreview)
{
RenderTarget->SetRenderTargetTexture(*(FTexture2DRHIRef*)InWindowBackBuffer);
{
// Check realtime mode for whether to pass current time to canvas
double CurrentTime = bIsRealtime ? (FApp::GetCurrentTime() - GStartTime) : 0.0;
float DeltaTime = bIsRealtime ? FApp::GetDeltaTime() : 0.0f;
FCanvas Canvas(RenderTarget, NULL, FGameTime::CreateUndilated(CurrentTime, DeltaTime), GMaxRHIFeatureLevel);
{
Canvas.SetAllowedModes(0);
Canvas.SetRenderTargetRect(RenderTarget->GetViewRect());
Canvas.SetRenderTargetScissorRect(RenderTarget->GetClippingRect());
FCanvasTileItem TileItem(FVector2D::ZeroVector, ExpressionPreview, RenderTarget->GetSizeXY());
Canvas.DrawItem(TileItem);
}
Canvas.Flush_RenderThread(RHICmdList, true);
}
RenderTarget->ClearRenderTargetTexture();
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
}
}
/////////////////////////////////////////////////////
// SGraphNodeMaterialBase
void SGraphNodeMaterialBase::Construct(const FArguments& InArgs, UMaterialGraphNode* InNode)
{
this->GraphNode = InNode;
this->MaterialNode = InNode;
this->SetCursor(EMouseCursor::CardinalCross);
this->UpdateGraphNode();
}
void SGraphNodeMaterialBase::CreatePinWidgets()
{
// Create Pin widgets for each of the pins.
for( int32 PinIndex=0; PinIndex < GraphNode->Pins.Num(); ++PinIndex )
{
UEdGraphPin* CurPin = GraphNode->Pins[PinIndex];
bool bHideNoConnectionPins = false;
if (OwnerGraphPanelPtr.IsValid())
{
bHideNoConnectionPins = OwnerGraphPanelPtr.Pin()->GetPinVisibility() == SGraphEditor::Pin_HideNoConnection;
}
const bool bPinHasConections = CurPin->LinkedTo.Num() > 0;
bool bPinDesiresToBeHidden = CurPin->bHidden || (bHideNoConnectionPins && !bPinHasConections);
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(GraphNode->GetGraph());
if (MaterialNode && MaterialNode->MaterialExpression && MaterialGraph->MaterialFunction == nullptr && !MaterialGraph->MaterialInputs.IsEmpty())
{
bool bIsAMakeAttrNode = MaterialNode->MaterialExpression->IsA(UMaterialExpressionMakeMaterialAttributes::StaticClass());
bool bIsABreakAttrNode = MaterialNode->MaterialExpression->IsA(UMaterialExpressionBreakMaterialAttributes::StaticClass());
if ((bIsABreakAttrNode && CurPin->Direction == EGPD_Output) || (bIsAMakeAttrNode && CurPin->Direction == EGPD_Input))
{
if (CurPin->PinType.PinCategory != UMaterialGraphSchema::PC_Exec)
{
bPinDesiresToBeHidden |= !MaterialGraph->MaterialInputs[CurPin->SourceIndex].IsVisiblePin(MaterialGraph->Material, true);
}
}
}
if (!bPinDesiresToBeHidden)
{
TSharedPtr<SGraphPin> NewPin = CreatePinWidget(CurPin);
check(NewPin.IsValid());
// Assign an custom icon to not connectible pins
if (CurPin->bNotConnectable)
{
if (!CacheImg_Pin_NotConnectable)
{
CacheImg_Pin_NotConnectable = FAppStyle::Get().GetBrush(NAME_Pin_NotConnectable);
}
NewPin->SetCustomPinIcon(CacheImg_Pin_NotConnectable, CacheImg_Pin_NotConnectable);
}
// Override pin color for Substrate node
if (Strata::IsStrataEnabled())
{
FSubstrateWidget::GetPinColor(NewPin, MaterialNode);
}
this->AddPin(NewPin.ToSharedRef());
}
}
}
void SGraphNodeMaterialBase::MoveTo(const FVector2D& NewPosition, FNodeSet& NodeFilter, bool bMarkDirty)
{
SGraphNode::MoveTo(NewPosition, NodeFilter, bMarkDirty);
MaterialNode->MaterialExpression->MaterialExpressionEditorX = MaterialNode->NodePosX;
MaterialNode->MaterialExpression->MaterialExpressionEditorY = MaterialNode->NodePosY;
MaterialNode->MaterialExpression->MarkPackageDirty();
MaterialNode->MaterialDirtyDelegate.ExecuteIfBound();
}
void SGraphNodeMaterialBase::AddPin( const TSharedRef<SGraphPin>& PinToAdd )
{
PinToAdd->SetOwner( SharedThis(this) );
// Set visibility on advanced view pins
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = (PinObj != nullptr) && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
}
if (PinToAdd->GetDirection() == EEdGraphPinDirection::EGPD_Input)
{
FMargin Padding = Settings->GetInputPinPadding();
Padding.Left *= 0.5f;
Padding.Right = 0.0f;
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(Padding)
[
PinToAdd
];
InputPins.Add(PinToAdd);
}
else // Direction == EEdGraphPinDirection::EGPD_Output
{
FMargin Padding = Settings->GetOutputPinPadding();
Padding.Left = 0.0f;
Padding.Right *= 0.5f;
RightNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(Padding)
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
void SGraphNodeMaterialBase::CreateBelowPinControls(TSharedPtr<SVerticalBox> MainBox)
{
if (GraphNode && MainBox.IsValid())
{
// Count the number of visible input pins on the left
int32 LeftPinCount = 0;
if (GraphNode->AdvancedPinDisplay == ENodeAdvancedPins::Hidden)
{
// Advanced view pins are hidden so exclude them from the pin count
for (int32 i = 0; i < InputPins.Num(); ++i)
{
const UEdGraphPin* PinObj = InputPins[i]->GetPinObj();
if (!PinObj->bAdvancedView)
{
LeftPinCount++;
}
}
}
else
{
LeftPinCount = InputPins.Num();
}
int32 RightPinCount = OutputPins.Num();
const float NegativeHPad = FMath::Max<float>(-Settings->PaddingTowardsNodeEdge, 0.0f);
const float ExtraPad = 0.0f;
// Place preview widget based on where the least pins are
if ((LeftPinCount < RightPinCount) || (RightPinCount == 0))
{
LeftNodeBox->AddSlot()
.Padding(FMargin(NegativeHPad + ExtraPad, 0.0f, 0.0f, 0.0f))
.AutoHeight()
.HAlign(HAlign_Left)
[
CreatePreviewWidget()
];
}
else if (LeftPinCount > RightPinCount)
{
RightNodeBox->AddSlot()
.Padding(FMargin(NegativeHPad + ExtraPad, 0.0f, 0.0f, 0.0f))
.AutoHeight()
.HAlign(HAlign_Right)
[
CreatePreviewWidget()
];
}
else
{
MainBox->AddSlot()
.Padding(Settings->GetNonPinNodeBodyPadding())
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
CreatePreviewWidget()
]
];
}
}
// Preview of Substrate nodes topology
if (Strata::IsStrataEnabled() && MaterialNode && MaterialNode->MaterialExpression->IsA(UMaterialExpressionStrataBSDF::StaticClass()))
{
if (const UMaterialExpressionStrataBSDF* StrataExpression = (const UMaterialExpressionStrataBSDF*)MaterialNode->MaterialExpression)
{
if (UMaterial* MaterialForStats = StrataExpression->Material)
{
if (const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(GMaxRHIFeatureLevel))
{
if (FMaterialShaderMap* ShaderMap = MaterialResource->GetGameThreadShaderMap())
{
const FStrataMaterialCompilationOutput& CompilationOutput = ShaderMap->GetStrataMaterialCompilationOutput();
MainBox->AddSlot()
.Padding(Settings->GetNonPinNodeBodyPadding())
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
FSubstrateWidget::ProcessOperator(CompilationOutput, StrataExpression->MaterialExpressionGuid)
]
];
}
}
}
}
}
}
void SGraphNodeMaterialBase::SetDefaultTitleAreaWidget(TSharedRef<SOverlay> DefaultTitleAreaWidget)
{
if (!MaterialNode->MaterialExpression->bHidePreviewWindow)
{
DefaultTitleAreaWidget->AddSlot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(FMargin(5))
[
SNew(SCheckBox)
.OnCheckStateChanged( this, &SGraphNodeMaterialBase::OnExpressionPreviewChanged )
.IsChecked( IsExpressionPreviewChecked() )
.Cursor(EMouseCursor::Default)
.Style(FAppStyle::Get(), "Graph.Node.AdvancedView")
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
[
SNew(SImage)
. Image(GetExpressionPreviewArrow())
]
]
];
}
}
TSharedRef<SWidget> SGraphNodeMaterialBase::CreateNodeContentArea()
{
// NODE CONTENT AREA
return SNew(SBorder)
.BorderImage( FAppStyle::GetBrush("NoBorder") )
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.HAlign(HAlign_Left)
.FillWidth(1.0f)
[
// LEFT
SAssignNew(LeftNodeBox, SVerticalBox)
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
[
// RIGHT
SAssignNew(RightNodeBox, SVerticalBox)
]
];
}
void SGraphNodeMaterialBase::OnAdvancedViewChanged(const ECheckBoxState NewCheckedState)
{
SGraphNode::OnAdvancedViewChanged(NewCheckedState);
// Update the graph node so that the preview is recreated to update its position
UpdateGraphNode();
}
TSharedRef<SWidget> SGraphNodeMaterialBase::CreatePreviewWidget()
{
PreviewViewport.Reset();
// if this node should currently show a preview
if (!MaterialNode->MaterialExpression->bHidePreviewWindow && !MaterialNode->MaterialExpression->bCollapsed)
{
const float ExpressionPreviewSize = 106.0f;
const float CentralPadding = 5.0f;
TSharedPtr<SViewport> ViewportWidget =
SNew( SViewport )
.RenderDirectlyToWindow(true)
.EnableGammaCorrection(false);
PreviewViewport = MakeShareable(new FPreviewViewport(MaterialNode));
// The viewport widget needs an interface so it knows what should render
ViewportWidget->SetViewportInterface( PreviewViewport.ToSharedRef() );
return SNew(SBox)
.WidthOverride(ExpressionPreviewSize)
.HeightOverride(ExpressionPreviewSize)
.MaxAspectRatio(1.0f)
.MaxDesiredHeight(ExpressionPreviewSize)
.Visibility(ExpressionPreviewVisibility())
[
SNew(SBorder)
.Padding(CentralPadding)
.BorderImage( FAppStyle::GetBrush("NoBorder") )
[
SNew(SOverlay)
+ SOverlay::Slot()
[
ViewportWidget.ToSharedRef()
]
+ SOverlay::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Bottom)
.Padding(0.0f,0.0f,8.0f,0.0f)
[
SNew(STextBlock)
.ShadowColorAndOpacity(FLinearColor::Black)
.ShadowOffset(FVector2D(1.0f, 1.0f))
.Text(this, &SGraphNodeMaterialBase::ExpressionPreviewOverlayText)
]
]
];
}
return SNullWidget::NullWidget;
}
FText SGraphNodeMaterialBase::ExpressionPreviewOverlayText() const
{
UMaterialExpression* MaterialExpression = MaterialNode->MaterialExpression;
return MaterialNode->MaterialExpression->GetPreviewOverlayText();
}
EVisibility SGraphNodeMaterialBase::ExpressionPreviewVisibility() const
{
UMaterialExpression* MaterialExpression = MaterialNode->MaterialExpression;
const bool bShowPreview = !MaterialExpression->bHidePreviewWindow && !MaterialExpression->bCollapsed;
return bShowPreview ? EVisibility::Visible : EVisibility::Collapsed;
}
void SGraphNodeMaterialBase::OnExpressionPreviewChanged( const ECheckBoxState NewCheckedState )
{
UMaterialExpression* MaterialExpression = MaterialNode->MaterialExpression;
const bool bCollapsed = (NewCheckedState != ECheckBoxState::Checked);
if (MaterialExpression->bCollapsed != bCollapsed)
{
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(MaterialNode->GetGraph());
MaterialGraph->ToggleCollapsedDelegate.ExecuteIfBound(MaterialExpression);
// Update the graph node so that preview viewport is created
UpdateGraphNode();
}
}
ECheckBoxState SGraphNodeMaterialBase::IsExpressionPreviewChecked() const
{
return MaterialNode->MaterialExpression->bCollapsed ? ECheckBoxState::Unchecked : ECheckBoxState::Checked;
}
const FSlateBrush* SGraphNodeMaterialBase::GetExpressionPreviewArrow() const
{
return FAppStyle::GetBrush(MaterialNode->MaterialExpression->bCollapsed ? TEXT("Icons.ChevronDown") : TEXT("Icons.ChevronUp"));
}
void SGraphNodeMaterialBase::PopulateMetaTag(FGraphNodeMetaData* TagMeta) const
{
if (GraphNode != nullptr)
{
UMaterialGraph* OuterGraph = MaterialNode->GetTypedOuter<UMaterialGraph>();
if ((OuterGraph != nullptr) && (MaterialNode->MaterialExpression != nullptr) )
{
TagMeta->OuterName = OuterGraph->OriginalMaterialFullName;
TagMeta->GUID = MaterialNode->MaterialExpression->MaterialExpressionGuid;
TagMeta->Tag = FName(*FString::Printf(TEXT("MaterialExprNode_%s_%s"), *TagMeta->OuterName, *TagMeta->GUID.ToString()));
}
TagMeta->FriendlyName = FString::Printf(TEXT("%s expression node in %s"), *GraphNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *TagMeta->OuterName);
}
}