Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/MaterialNodes/SGraphNodeMaterialBase.cpp

482 lines
14 KiB
C++
Raw Normal View History

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "GraphEditorCommon.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialFunction.h"
#include "SGraphNodeMaterialBase.h"
#include "ScopedTransaction.h"
#include "Runtime/Engine/Public/Slate/SceneViewport.h"
#include "TutorialMetaData.h"
#include "CanvasTypes.h"
#include "CanvasItem.h"
/**
* 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 )
{
}
FPreviewViewport::~FPreviewViewport()
{
// Pass the preview element to the render thread so that it's deleted after it's shown for the last time
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER
(
SafeDeletePreviewElement,
FThreadSafePreviewPtr, PreviewElementPtr, PreviewElement,
{
PreviewElementPtr.Reset();
}
);
}
void FPreviewViewport::OnDrawViewport( const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, class FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled )
{
FSlateRect SlateCanvasRect = AllottedGeometry.GetClippingRect();
FSlateRect ClippedCanvasRect = SlateCanvasRect.IntersectionWith(MyClippingRect);
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->GetExpressionPreview(), bIsRealtime ))
{
// Draw above everything else
uint32 PreviewLayer = LayerId+1;
FSlateDrawElement::MakeCustom( OutDrawElements, PreviewLayer, PreviewElement );
}
}
FIntPoint FPreviewViewport::GetSize() const
{
return FIntPoint(96,96);
}
/////////////////////////////////////////////////////
// FPreviewElement
FPreviewElement::FPreviewElement()
: RenderTarget(new FSlateMaterialPreviewRenderTarget)
, ExpressionPreview(NULL)
, bIsRealtime(false)
{
}
FPreviewElement::~FPreviewElement()
{
delete RenderTarget;
}
bool FPreviewElement::BeginRenderingCanvas( const FIntRect& InCanvasRect, const FIntRect& InClippingRect, FMaterialRenderProxy* InExpressionPreview, bool bInIsRealtime )
{
if(InCanvasRect.Size().X > 0 && InCanvasRect.Size().Y > 0 && InClippingRect.Size().X > 0 && InClippingRect.Size().Y > 0 && InExpressionPreview != 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* ExpressionPreview;
/** Whether preview is using realtime values */
bool bIsRealtime;
};
FPreviewRenderInfo RenderInfo;
RenderInfo.CanvasRect = InCanvasRect;
RenderInfo.ClippingRect = InClippingRect;
RenderInfo.ExpressionPreview = InExpressionPreview;
RenderInfo.bIsRealtime = bInIsRealtime;
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER
(
BeginRenderingPreviewCanvas,
FPreviewElement*, PreviewElement, this,
FPreviewRenderInfo, InRenderInfo, RenderInfo,
{
PreviewElement->RenderTarget->SetViewRect(InRenderInfo.CanvasRect);
PreviewElement->RenderTarget->SetClippingRect(InRenderInfo.ClippingRect);
PreviewElement->ExpressionPreview = InRenderInfo.ExpressionPreview;
PreviewElement->bIsRealtime = InRenderInfo.bIsRealtime;
}
);
return true;
}
return false;
}
void FPreviewElement::DrawRenderThread(FRHICommandListImmediate& RHICmdList, const void* InWindowBackBuffer)
{
// Clip the canvas to avoid having to set UV values
FIntRect ClippingRect = RenderTarget->GetClippingRect();
RHICmdList.SetScissorRect(true,
ClippingRect.Min.X,
ClippingRect.Min.Y,
ClippingRect.Max.X,
ClippingRect.Max.Y);
RenderTarget->SetRenderTargetTexture( *(FTexture2DRHIRef*)InWindowBackBuffer );
{
// Check realtime mode for whether to pass current time to canvas
float CurrentTime = bIsRealtime ? (FApp::GetCurrentTime() - GStartTime) : 0.0f;
float DeltaTime = bIsRealtime ? FApp::GetDeltaTime() : 0.0f;
FCanvas Canvas(RenderTarget, NULL, CurrentTime, CurrentTime, DeltaTime, GMaxRHIFeatureLevel);
{
Canvas.SetAllowedModes( 0 );
Canvas.SetRenderTargetRect( RenderTarget->GetViewRect() );
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;
const bool bPinDesiresToBeHidden = CurPin->bHidden || (bHideNoConnectionPins && !bPinHasConections);
if (!bPinDesiresToBeHidden)
{
TSharedPtr<SGraphPin> NewPin = CreatePinWidget(CurPin);
check(NewPin.IsValid());
NewPin->SetIsEditable(IsEditable);
this->AddPin(NewPin.ToSharedRef());
}
}
}
void SGraphNodeMaterialBase::MoveTo(const FVector2D& NewPosition, FNodeSet& NodeFilter)
{
SGraphNode::MoveTo(NewPosition, NodeFilter);
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) );
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())
{
int32 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()
[
CreatePreviewWidget()
];
}
else if (LeftPinCount > RightPinCount)
{
RightNodeBox->AddSlot()
.Padding(FMargin(NegativeHPad + ExtraPad, 0.0f, 0.0f, 0.0f))
.AutoHeight()
[
CreatePreviewWidget()
];
}
else
{
MainBox->AddSlot()
.Padding(Settings->GetNonPinNodeBodyPadding())
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
CreatePreviewWidget()
]
];
}
}
}
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(FEditorStyle::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( FEditorStyle::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)
]
];
}
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 )
.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)
.Visibility(ExpressionPreviewVisibility())
[
SNew(SBorder)
.Padding(CentralPadding)
.BorderImage( FEditorStyle::GetBrush("NoBorder") )
[
ViewportWidget.ToSharedRef()
]
];
}
return SNullWidget::NullWidget;
}
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 FEditorStyle::GetBrush(MaterialNode->MaterialExpression->bCollapsed ? TEXT("Kismet.TitleBarEditor.ArrowDown") : TEXT("Kismet.TitleBarEditor.ArrowUp"));
}
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);
}
}