Files
UnrealEngineUWP/Engine/Plugins/Animation/ControlRig/Source/ControlRigEditor/Private/ControlRigGraphDetails.cpp
sara schvartzman c2e8517589 Control Rig: Fix cannot add execute context to a function once it is removed
#jira UE-135600
#rb helge.mathee
#preflight 61a8c1afe8314ee7b594cb36

#ROBOMERGE-AUTHOR: sara.schvartzman
#ROBOMERGE-SOURCE: CL 18353073 in //UE5/Release-5.0/... via CL 18353095
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v895-18170469)
#ROBOMERGE[STARSHIP]: UE5-Main

[CL 18353121 by sara schvartzman in ue5-release-engine-test branch]
2021-12-02 11:44:22 -05:00

1503 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ControlRigGraphDetails.h"
#include "Widgets/SWidget.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "DetailWidgetRow.h"
#include "IDetailChildrenBuilder.h"
#include "EditorStyleSet.h"
#include "SPinTypeSelector.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Colors/SColorPicker.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Text/SMultiLineEditableText.h"
#include "PropertyCustomizationHelpers.h"
#include "NodeFactory.h"
#include "Graph/ControlRigGraphNode.h"
#include "ControlRig.h"
#include "RigVMCore/RigVMExternalVariable.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Graph/ControlRigGraphSchema.h"
#include "EditorCategoryUtils.h"
#include "IPropertyUtilities.h"
#include "Graph/SControlRigGraphPinVariableBinding.h"
#define LOCTEXT_NAMESPACE "ControlRigGraphDetails"
FControlRigArgumentGroupLayout::FControlRigArgumentGroupLayout(
URigVMGraph* InGraph,
UControlRigBlueprint* InBlueprint,
TWeakPtr<IControlRigEditor> InEditor,
bool bInputs)
: GraphPtr(InGraph)
, ControlRigBlueprintPtr(InBlueprint)
, ControlRigEditorPtr(InEditor)
, bIsInputGroup(bInputs)
{
if (ControlRigBlueprintPtr.IsValid())
{
ControlRigBlueprintPtr.Get()->OnModified().AddRaw(this, &FControlRigArgumentGroupLayout::HandleModifiedEvent);
}
}
FControlRigArgumentGroupLayout::~FControlRigArgumentGroupLayout()
{
if (ControlRigBlueprintPtr.IsValid())
{
ControlRigBlueprintPtr.Get()->OnModified().RemoveAll(this);
}
}
void FControlRigArgumentGroupLayout::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder)
{
bool WasContentAdded = false;
if (GraphPtr.IsValid())
{
URigVMGraph* Graph = GraphPtr.Get();
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Graph->GetOuter()))
{
for (URigVMPin* Pin : LibraryNode->GetPins())
{
if ((bIsInputGroup && (Pin->GetDirection() == ERigVMPinDirection::Input || Pin->GetDirection() == ERigVMPinDirection::IO)) ||
(!bIsInputGroup && (Pin->GetDirection() == ERigVMPinDirection::Output || Pin->GetDirection() == ERigVMPinDirection::IO)))
{
TSharedRef<class FControlRigArgumentLayout> ControlRigArgumentLayout = MakeShareable(new FControlRigArgumentLayout(
Pin,
Graph,
ControlRigBlueprintPtr.Get(),
ControlRigEditorPtr
));
ChildrenBuilder.AddCustomBuilder(ControlRigArgumentLayout);
WasContentAdded = true;
}
}
}
}
if (!WasContentAdded)
{
// Add a text widget to let the user know to hit the + icon to add parameters.
ChildrenBuilder.AddCustomRow(FText::GetEmpty()).WholeRowContent()
.MaxDesiredWidth(980.f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.AutoWidth()
[
SNew(STextBlock)
.Text(LOCTEXT("NoArgumentsAddedForControlRig", "Please press the + icon above to add parameters"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
];
}
}
void FControlRigArgumentGroupLayout::HandleModifiedEvent(ERigVMGraphNotifType InNotifType, URigVMGraph* InGraph, UObject* InSubject)
{
if (!GraphPtr.IsValid())
{
return;
}
URigVMGraph* Graph = GraphPtr.Get();
URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Graph->GetOuter());
if (LibraryNode == nullptr)
{
return;
}
switch (InNotifType)
{
case ERigVMGraphNotifType::PinAdded:
case ERigVMGraphNotifType::PinRemoved:
case ERigVMGraphNotifType::PinIndexChanged:
case ERigVMGraphNotifType::PinTypeChanged:
{
URigVMPin* Pin = CastChecked<URigVMPin>(InSubject);
if (Pin->GetNode() == LibraryNode)
{
OnRebuildChildren.ExecuteIfBound();
}
break;
}
default:
{
break;
}
}
}
class FControlRigArgumentPinTypeSelectorFilter : public IPinTypeSelectorFilter
{
public:
FControlRigArgumentPinTypeSelectorFilter(TWeakPtr<IControlRigEditor> InControlRigEditor, TWeakObjectPtr<URigVMGraph> InGraph)
: ControlRigEditorPtr(InControlRigEditor), GraphPtr(InGraph)
{
}
virtual bool ShouldShowPinTypeTreeItem(FPinTypeTreeItem InItem) const override
{
if (!InItem.IsValid())
{
return false;
}
// Only allow an execute context pin if the graph doesnt have one already
FString CPPType;
UObject* CPPTypeObject = nullptr;
RigVMTypeUtils::CPPTypeFromPinType(InItem.Get()->GetPinType(false), CPPType, &CPPTypeObject);
if (UScriptStruct* ScriptStruct = Cast<UScriptStruct>(CPPTypeObject))
{
if (ScriptStruct->IsChildOf(FRigVMExecuteContext::StaticStruct()))
{
if (GraphPtr.IsValid())
{
if (URigVMFunctionEntryNode* EntryNode = GraphPtr.Get()->GetEntryNode())
{
for (URigVMPin* Pin : EntryNode->GetPins())
{
if (Pin->IsExecuteContext())
{
return false;
}
}
}
}
}
}
if (ControlRigEditorPtr.IsValid())
{
if (ControlRigEditorPtr.Pin()->GetImportedPinTypeSelectorFilter().IsValid())
{
return ControlRigEditorPtr.Pin()->GetImportedPinTypeSelectorFilter()->ShouldShowPinTypeTreeItem(InItem);
}
}
return false;
}
private:
TWeakPtr<IControlRigEditor> ControlRigEditorPtr;
TWeakObjectPtr<URigVMGraph> GraphPtr;
};
void FControlRigArgumentLayout::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow)
{
const UEdGraphSchema* Schema = GetDefault<UControlRigGraphSchema>();
ETypeTreeFilter TypeTreeFilter = ETypeTreeFilter::None;
TypeTreeFilter |= ETypeTreeFilter::AllowExec;
TSharedPtr<IPinTypeSelectorFilter> CustomPinTypeFilter;
if (ControlRigEditorPtr.IsValid())
{
CustomPinTypeFilter = MakeShared<FControlRigArgumentPinTypeSelectorFilter>(ControlRigEditorPtr, GraphPtr);
}
NodeRow
.NameContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1)
.VAlign(VAlign_Center)
[
SAssignNew(ArgumentNameWidget, SEditableTextBox)
.Text(this, &FControlRigArgumentLayout::OnGetArgNameText)
.OnTextCommitted(this, &FControlRigArgumentLayout::OnArgNameTextCommitted)
.ToolTipText(this, &FControlRigArgumentLayout::OnGetArgToolTipText)
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(!ShouldPinBeReadOnly())
.OnVerifyTextChanged_Lambda([&](const FText& InNewText, FText& OutErrorMessage) -> bool
{
if (InNewText.IsEmpty())
{
OutErrorMessage = LOCTEXT("ArgumentNameEmpty", "Cannot have an argument with an emtpy string name.");
return false;
}
else if (InNewText.ToString().Len() >= NAME_SIZE)
{
OutErrorMessage = LOCTEXT("ArgumentNameTooLong", "Name of argument is too long.");
return false;
}
EValidatorResult Result = NameValidator.IsValid(InNewText.ToString(), false);
OutErrorMessage = INameValidatorInterface::GetErrorText(InNewText.ToString(), Result);
return Result == EValidatorResult::Ok || Result == EValidatorResult::ExistingName;
})
]
]
.ValueContent()
.MaxDesiredWidth(980.f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
.AutoWidth()
[
SNew(SPinTypeSelector, FGetPinTypeTree::CreateUObject(GetDefault<UEdGraphSchema_K2>(), &UEdGraphSchema_K2::GetVariableTypeTree))
.TargetPinType(this, &FControlRigArgumentLayout::OnGetPinInfo)
.OnPinTypePreChanged(this, &FControlRigArgumentLayout::OnPrePinInfoChange)
.OnPinTypeChanged(this, &FControlRigArgumentLayout::PinInfoChanged)
.Schema(Schema)
.TypeTreeFilter(TypeTreeFilter)
.bAllowArrays(!ShouldPinBeReadOnly())
.IsEnabled(!ShouldPinBeReadOnly(true))
.CustomFilter(CustomPinTypeFilter)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), TEXT("SimpleButton"))
.ContentPadding(0)
.IsEnabled(!IsPinEditingReadOnly())
.OnClicked(this, &FControlRigArgumentLayout::OnArgMoveUp)
.ToolTipText(LOCTEXT("FunctionArgDetailsArgMoveUpTooltip", "Move this parameter up in the list."))
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Icons.ChevronUp"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), TEXT("SimpleButton"))
.ContentPadding(0)
.IsEnabled(!IsPinEditingReadOnly())
.OnClicked(this, &FControlRigArgumentLayout::OnArgMoveDown)
.ToolTipText(LOCTEXT("FunctionArgDetailsArgMoveDownTooltip", "Move this parameter down in the list."))
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("Icons.ChevronDown"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
+ SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(10, 0, 0, 0)
.AutoWidth()
[
PropertyCustomizationHelpers::MakeClearButton(FSimpleDelegate::CreateSP(this, &FControlRigArgumentLayout::OnRemoveClicked), LOCTEXT("FunctionArgDetailsClearTooltip", "Remove this parameter."), !IsPinEditingReadOnly())
]
];
}
void FControlRigArgumentLayout::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder)
{
// we don't show defaults here - we rely on a SControlRigGraphNode widget in the top of the details
}
void FControlRigArgumentLayout::OnRemoveClicked()
{
if (PinPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
URigVMPin* Pin = PinPtr.Get();
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Pin->GetNode()))
{
if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph()))
{
Controller->RemoveExposedPin(Pin->GetFName(), true, true);
}
}
}
}
FReply FControlRigArgumentLayout::OnArgMoveUp()
{
if (PinPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
URigVMPin* Pin = PinPtr.Get();
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Pin->GetNode()))
{
if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph()))
{
bool bIsInput = Pin->GetDirection() == ERigVMPinDirection::Input || Pin->GetDirection() == ERigVMPinDirection::IO;
int32 NewPinIndex = Pin->GetPinIndex() - 1;
while (NewPinIndex != INDEX_NONE)
{
URigVMPin* OtherPin = LibraryNode->GetPins()[NewPinIndex];
if (bIsInput)
{
if (OtherPin->GetDirection() == ERigVMPinDirection::Input || OtherPin->GetDirection() == ERigVMPinDirection::IO)
{
break;
}
}
else
{
if (OtherPin->GetDirection() == ERigVMPinDirection::Output || OtherPin->GetDirection() == ERigVMPinDirection::IO)
{
break;
}
}
--NewPinIndex;
}
if (NewPinIndex != INDEX_NONE)
{
Controller->SetExposedPinIndex(Pin->GetFName(), NewPinIndex, true, true);
}
return FReply::Handled();
}
}
}
return FReply::Unhandled();
}
FReply FControlRigArgumentLayout::OnArgMoveDown()
{
if (PinPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
URigVMPin* Pin = PinPtr.Get();
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Pin->GetNode()))
{
if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph()))
{
bool bIsInput = Pin->GetDirection() == ERigVMPinDirection::Input || Pin->GetDirection() == ERigVMPinDirection::IO;
int32 NewPinIndex = Pin->GetPinIndex() + 1;
while (NewPinIndex < LibraryNode->GetPins().Num())
{
URigVMPin* OtherPin = LibraryNode->GetPins()[NewPinIndex];
if (bIsInput)
{
if (OtherPin->GetDirection() == ERigVMPinDirection::Input || OtherPin->GetDirection() == ERigVMPinDirection::IO)
{
break;
}
}
else
{
if (OtherPin->GetDirection() == ERigVMPinDirection::Output || OtherPin->GetDirection() == ERigVMPinDirection::IO)
{
break;
}
}
++NewPinIndex;
}
if (NewPinIndex < LibraryNode->GetPins().Num())
{
Controller->SetExposedPinIndex(Pin->GetFName(), NewPinIndex, true, true);
}
return FReply::Handled();
}
}
}
return FReply::Unhandled();
}
bool FControlRigArgumentLayout::ShouldPinBeReadOnly(bool bIsEditingPinType/* = false*/) const
{
return IsPinEditingReadOnly(bIsEditingPinType);
}
bool FControlRigArgumentLayout::IsPinEditingReadOnly(bool bIsEditingPinType/* = false*/) const
{
/*
if (PinPtr.IsValid())
{
return PinPtr.Get()->IsExecuteContext();
}
*/
return false;
}
FText FControlRigArgumentLayout::OnGetArgNameText() const
{
if (PinPtr.IsValid())
{
return FText::FromName(PinPtr.Get()->GetFName());
}
return FText();
}
FText FControlRigArgumentLayout::OnGetArgToolTipText() const
{
return OnGetArgNameText(); // for now since we don't have tooltips
}
void FControlRigArgumentLayout::OnArgNameTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit)
{
if (!NewText.IsEmpty() && PinPtr.IsValid() && ControlRigBlueprintPtr.IsValid() && !ShouldPinBeReadOnly())
{
URigVMPin* Pin = PinPtr.Get();
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Pin->GetNode()))
{
if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph()))
{
const FString& NewName = NewText.ToString();
Controller->RenameExposedPin(Pin->GetFName(), *NewName, true, true);
}
}
}
}
FEdGraphPinType FControlRigArgumentLayout::OnGetPinInfo() const
{
if (PinPtr.IsValid())
{
return UControlRigGraphNode::GetPinTypeForModelPin(PinPtr.Get());
}
return FEdGraphPinType();
}
void FControlRigArgumentLayout::PinInfoChanged(const FEdGraphPinType& PinType)
{
if (PinPtr.IsValid() && ControlRigBlueprintPtr.IsValid() && FBlueprintEditorUtils::IsPinTypeValid(PinType))
{
URigVMPin* Pin = PinPtr.Get();
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Pin->GetNode()))
{
if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph()))
{
FString CPPType;
FName CPPTypeObjectName = NAME_None;
RigVMTypeUtils::CPPTypeFromPinType(PinType, CPPType, CPPTypeObjectName);
bool bSetupUndoRedo = true;
Controller->ChangeExposedPinType(Pin->GetFName(), CPPType, CPPTypeObjectName, bSetupUndoRedo, false, true);
// If the controller has identified this as a bulk change, it has not added the actions to the action stack
// We need to disable the transaction from the UI as well to keep them synced
if (!bSetupUndoRedo)
{
GEditor->CancelTransaction(0);
}
}
}
}
}
void FControlRigArgumentLayout::OnPrePinInfoChange(const FEdGraphPinType& PinType)
{
// not needed for Control Rig
}
FControlRigArgumentDefaultNode::FControlRigArgumentDefaultNode(
URigVMGraph* InGraph,
UControlRigBlueprint* InBlueprint
)
: GraphPtr(InGraph)
, ControlRigBlueprintPtr(InBlueprint)
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
ControlRigBlueprintPtr.Get()->OnModified().AddRaw(this, &FControlRigArgumentDefaultNode::HandleModifiedEvent);
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(GraphPtr->GetOuter()))
{
if (UControlRigGraph* RigGraph = Cast<UControlRigGraph>(ControlRigBlueprintPtr->GetEdGraph(LibraryNode->GetGraph())))
{
GraphChangedDelegateHandle = RigGraph->AddOnGraphChangedHandler(
FOnGraphChanged::FDelegate::CreateRaw(this, &FControlRigArgumentDefaultNode::OnGraphChanged)
);
}
}
}
}
FControlRigArgumentDefaultNode::~FControlRigArgumentDefaultNode()
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
ControlRigBlueprintPtr.Get()->OnModified().RemoveAll(this);
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(GraphPtr->GetOuter()))
{
if (UControlRigGraph* RigGraph = Cast<UControlRigGraph>(ControlRigBlueprintPtr->GetEdGraph(LibraryNode->GetGraph())))
{
if (GraphChangedDelegateHandle.IsValid())
{
RigGraph->RemoveOnGraphChangedHandler(GraphChangedDelegateHandle);
}
}
}
}
}
void FControlRigArgumentDefaultNode::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder)
{
if (!GraphPtr.IsValid() || !ControlRigBlueprintPtr.IsValid())
{
return;
}
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
URigVMGraph* Graph = GraphPtr.Get();
UControlRigGraphNode* ControlRigGraphNode = nullptr;
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Graph->GetOuter()))
{
if (UControlRigGraph* RigGraph = Cast<UControlRigGraph>(Blueprint->GetEdGraph(LibraryNode->GetGraph())))
{
ControlRigGraphNode = Cast<UControlRigGraphNode>(RigGraph->FindNodeForModelNodeName(LibraryNode->GetFName()));
}
}
if (ControlRigGraphNode == nullptr)
{
return;
}
ChildrenBuilder.AddCustomRow(FText::GetEmpty())
.WholeRowContent()
.MaxDesiredWidth(980.f)
[
SAssignNew(OwnedNodeWidget, SControlRigGraphNode).GraphNodeObj(ControlRigGraphNode)
];
OwnedNodeWidget->SetIsEditable(true);
TArray< TSharedRef<SWidget> > Pins;
OwnedNodeWidget->GetPins(Pins);
for (TSharedRef<SWidget> Pin : Pins)
{
TSharedRef<SGraphPin> SPin = StaticCastSharedRef<SGraphPin>(Pin);
SPin->GetPinObj()->bNotConnectable = true;
}
}
void FControlRigArgumentDefaultNode::OnGraphChanged(const FEdGraphEditAction& InAction)
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
OnRebuildChildren.ExecuteIfBound();
}
}
void FControlRigArgumentDefaultNode::HandleModifiedEvent(ERigVMGraphNotifType InNotifType, URigVMGraph* InGraph, UObject* InSubject)
{
if (!GraphPtr.IsValid())
{
return;
}
URigVMGraph* Graph = GraphPtr.Get();
URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Graph->GetOuter());
if (LibraryNode == nullptr)
{
return;
}
if (LibraryNode->GetGraph() != InGraph)
{
return;
}
switch (InNotifType)
{
case ERigVMGraphNotifType::PinAdded:
case ERigVMGraphNotifType::PinRemoved:
case ERigVMGraphNotifType::PinTypeChanged:
case ERigVMGraphNotifType::PinIndexChanged:
case ERigVMGraphNotifType::PinRenamed:
{
URigVMPin* Pin = CastChecked<URigVMPin>(InSubject);
if (Pin->GetNode() == LibraryNode)
{
OnRebuildChildren.ExecuteIfBound();
}
break;
}
case ERigVMGraphNotifType::NodeRenamed:
case ERigVMGraphNotifType::NodeColorChanged:
{
URigVMNode* Node = CastChecked<URigVMNode>(InSubject);
if (Node == LibraryNode)
{
OnRebuildChildren.ExecuteIfBound();
}
break;
}
default:
{
break;
}
}
}
TSharedPtr<IDetailCustomization> FControlRigGraphDetails::MakeInstance(TSharedPtr<IBlueprintEditor> InBlueprintEditor)
{
const TArray<UObject*>* Objects = (InBlueprintEditor.IsValid() ? InBlueprintEditor->GetObjectsCurrentlyBeingEdited() : nullptr);
if (Objects && Objects->Num() == 1)
{
if (UControlRigBlueprint* ControlRigBlueprint = Cast<UControlRigBlueprint>((*Objects)[0]))
{
return MakeShareable(new FControlRigGraphDetails(StaticCastSharedPtr<IControlRigEditor>(InBlueprintEditor), ControlRigBlueprint));
}
}
return nullptr;
}
void FControlRigGraphDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
bIsPickingColor = false;
TArray<TWeakObjectPtr<UObject>> Objects;
DetailLayout.GetObjectsBeingCustomized(Objects);
GraphPtr = CastChecked<UControlRigGraph>(Objects[0].Get());
UControlRigGraph* Graph = GraphPtr.Get();
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
URigVMGraph* Model = nullptr;
URigVMController* Controller = nullptr;
if (Blueprint)
{
Model = Blueprint->GetModel(Graph);
Controller = Blueprint->GetController(Model);
}
if (Blueprint == nullptr || Model == nullptr || Controller == nullptr)
{
IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Graph", LOCTEXT("FunctionDetailsGraph", "Graph"));
Category.AddCustomRow(FText::GetEmpty())
[
SNew(STextBlock)
.Text(LOCTEXT("GraphPresentButNotEditable", "Graph is not editable."))
];
return;
}
if (Model->IsTopLevelGraph())
{
IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Graph", LOCTEXT("FunctionDetailsGraph", "Graph"));
Category.AddCustomRow(FText::GetEmpty())
[
SNew(STextBlock)
.Text(LOCTEXT("GraphIsTopLevelGraph", "Top-level Graphs are not editable."))
];
return;
}
IDetailCategoryBuilder& InputsCategory = DetailLayout.EditCategory("Inputs", LOCTEXT("FunctionDetailsInputs", "Inputs"));
TSharedRef<FControlRigArgumentGroupLayout> InputArgumentGroup = MakeShareable(new FControlRigArgumentGroupLayout(
Model,
Blueprint,
ControlRigEditorPtr,
true));
InputsCategory.AddCustomBuilder(InputArgumentGroup);
TSharedRef<SHorizontalBox> InputsHeaderContentWidget = SNew(SHorizontalBox);
InputsHeaderContentWidget->AddSlot()
.HAlign(HAlign_Right)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "SimpleButton")
.ContentPadding(FMargin(1, 0))
.OnClicked(this, &FControlRigGraphDetails::OnAddNewInputClicked)
.Visibility(this, &FControlRigGraphDetails::GetAddNewInputOutputVisibility)
.HAlign(HAlign_Right)
.ToolTipText(LOCTEXT("FunctionNewInputArgTooltip", "Create a new input argument"))
.VAlign(VAlign_Center)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("FunctionNewInputArg")))
.IsEnabled(this, &FControlRigGraphDetails::IsAddNewInputOutputEnabled)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
InputsCategory.HeaderContent(InputsHeaderContentWidget);
IDetailCategoryBuilder& OutputsCategory = DetailLayout.EditCategory("Outputs", LOCTEXT("FunctionDetailsOutputs", "Outputs"));
TSharedRef<FControlRigArgumentGroupLayout> OutputArgumentGroup = MakeShareable(new FControlRigArgumentGroupLayout(
Model,
Blueprint,
ControlRigEditorPtr,
false));
OutputsCategory.AddCustomBuilder(OutputArgumentGroup);
TSharedRef<SHorizontalBox> OutputsHeaderContentWidget = SNew(SHorizontalBox);
OutputsHeaderContentWidget->AddSlot()
.HAlign(HAlign_Right)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "SimpleButton")
.ContentPadding(FMargin(1, 0))
.OnClicked(this, &FControlRigGraphDetails::OnAddNewOutputClicked)
.Visibility(this, &FControlRigGraphDetails::GetAddNewInputOutputVisibility)
.HAlign(HAlign_Right)
.ToolTipText(LOCTEXT("FunctionNewOutputArgTooltip", "Create a new output argument"))
.VAlign(VAlign_Center)
.AddMetaData<FTagMetaData>(FTagMetaData(TEXT("FunctionNewOutputArg")))
.IsEnabled(this, &FControlRigGraphDetails::IsAddNewInputOutputEnabled)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
OutputsCategory.HeaderContent(OutputsHeaderContentWidget);
IDetailCategoryBuilder& SettingsCategory = DetailLayout.EditCategory("NodeSettings", LOCTEXT("FunctionDetailsNodeSettings", "Node Settings"));
bool bIsFunction = false;
if (Model)
{
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Model->GetOuter()))
{
bIsFunction = LibraryNode->GetGraph()->IsA<URigVMFunctionLibrary>();
}
}
if(bIsFunction)
{
// node category
SettingsCategory.AddCustomRow(FText::GetEmpty())
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Category")))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SEditableTextBox)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FControlRigGraphDetails::GetNodeCategory)
.OnTextCommitted(this, &FControlRigGraphDetails::SetNodeCategory)
.OnVerifyTextChanged_Lambda([&](const FText& InNewText, FText& OutErrorMessage) -> bool
{
const FText NewText = FEditorCategoryUtils::GetCategoryDisplayString(InNewText);
if (NewText.ToString().Len() >= NAME_SIZE)
{
OutErrorMessage = LOCTEXT("CategoryTooLong", "Name of category is too long.");
return false;
}
if (ControlRigBlueprintPtr.IsValid())
{
if (NewText.EqualTo(FText::FromString(ControlRigBlueprintPtr.Get()->GetName())))
{
OutErrorMessage = LOCTEXT("CategoryEqualsBlueprintName", "Cannot add a category with the same name as the blueprint.");
return false;
}
}
return true;
})
];
// node keywords
SettingsCategory.AddCustomRow(FText::GetEmpty())
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Keywords")))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SEditableTextBox)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FControlRigGraphDetails::GetNodeKeywords)
.OnTextCommitted(this, &FControlRigGraphDetails::SetNodeKeywords)
];
// description
SettingsCategory.AddCustomRow(FText::GetEmpty())
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Description")))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SMultiLineEditableText)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FControlRigGraphDetails::GetNodeDescription)
.OnTextCommitted(this, &FControlRigGraphDetails::SetNodeDescription)
];
if(AccessSpecifierStrings.IsEmpty())
{
AccessSpecifierStrings.Add(TSharedPtr<FString>(new FString(TEXT("Public"))));
AccessSpecifierStrings.Add(TSharedPtr<FString>(new FString(TEXT("Private"))));
}
// access specifier
SettingsCategory.AddCustomRow( LOCTEXT( "AccessSpecifier", "Access Specifier" ) )
.NameContent()
[
SNew(STextBlock)
.Text( LOCTEXT( "AccessSpecifier", "Access Specifier" ) )
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
.ValueContent()
[
SNew(SComboButton)
.ContentPadding(0)
.ButtonContent()
[
SNew(STextBlock)
.Text(this, &FControlRigGraphDetails::GetCurrentAccessSpecifierName)
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
.MenuContent()
[
SNew(SListView<TSharedPtr<FString> >)
.ListItemsSource( &AccessSpecifierStrings )
.OnGenerateRow(this, &FControlRigGraphDetails::HandleGenerateRowAccessSpecifier)
.OnSelectionChanged(this, &FControlRigGraphDetails::OnAccessSpecifierSelected)
]
];
}
// node color
SettingsCategory.AddCustomRow(FText::GetEmpty())
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Color")))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "Menu.Button")
.OnClicked(this, &FControlRigGraphDetails::OnNodeColorClicked)
[
SAssignNew(ColorBlock, SColorBlock)
.Color(this, &FControlRigGraphDetails::GetNodeColor)
.Size(FVector2D(77, 16))
]
];
IDetailCategoryBuilder& DefaultsCategory = DetailLayout.EditCategory("NodeDefaults", LOCTEXT("FunctionDetailsNodeDefaults", "Node Defaults"));
TSharedRef<FControlRigArgumentDefaultNode> DefaultsArgumentNode = MakeShareable(new FControlRigArgumentDefaultNode(
Model,
Blueprint));
DefaultsCategory.AddCustomBuilder(DefaultsArgumentNode);
}
bool FControlRigGraphDetails::IsAddNewInputOutputEnabled() const
{
return true;
}
EVisibility FControlRigGraphDetails::GetAddNewInputOutputVisibility() const
{
return EVisibility::Visible;
}
FReply FControlRigGraphDetails::OnAddNewInputClicked()
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get());
if (URigVMController* Controller = Blueprint->GetController(Model))
{
FName ArgumentName = TEXT("Argument");
FString CPPType = TEXT("bool");
FName CPPTypeObjectPath = NAME_None;
FString DefaultValue = TEXT("False");
if (URigVMLibraryNode* LibraryNode = Cast<URigVMLibraryNode>(Model->GetOuter()))
{
if (LibraryNode->GetPins().Num() > 0)
{
URigVMPin* LastPin = LibraryNode->GetPins().Last();
if (!LastPin->IsExecuteContext())
{
ArgumentName = LastPin->GetFName();
CPPType = LastPin->GetCPPType();
if (LastPin->GetCPPTypeObject())
{
CPPTypeObjectPath = *LastPin->GetCPPTypeObject()->GetPathName();
}
DefaultValue = LastPin->GetDefaultValue();
}
}
}
Controller->AddExposedPin(ArgumentName, ERigVMPinDirection::Input, CPPType, CPPTypeObjectPath, DefaultValue, true, true);
}
}
return FReply::Unhandled();
}
FReply FControlRigGraphDetails::OnAddNewOutputClicked()
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get());
if (URigVMController* Controller = Blueprint->GetController(Model))
{
FName ArgumentName = TEXT("Argument");
FString CPPType = TEXT("bool");
FName CPPTypeObjectPath = NAME_None;
FString DefaultValue = TEXT("False");
// todo: base decisions on types on last argument
Controller->AddExposedPin(ArgumentName, ERigVMPinDirection::Output, CPPType, CPPTypeObjectPath, DefaultValue, true, true);
}
}
return FReply::Unhandled();
}
FText FControlRigGraphDetails::GetNodeCategory() const
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
return FText::FromString(OuterNode->GetNodeCategory());
}
}
}
return FText();
}
void FControlRigGraphDetails::SetNodeCategory(const FText& InNewText, ETextCommit::Type InCommitType)
{
if(InCommitType == ETextCommit::OnCleared)
{
return;
}
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph()))
{
Controller->SetNodeCategory(OuterNode, InNewText.ToString(), true, false, true);
}
}
}
}
}
FText FControlRigGraphDetails::GetNodeKeywords() const
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
return FText::FromString(OuterNode->GetNodeKeywords());
}
}
}
return FText();
}
void FControlRigGraphDetails::SetNodeKeywords(const FText& InNewText, ETextCommit::Type InCommitType)
{
if(InCommitType == ETextCommit::OnCleared)
{
return;
}
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph()))
{
Controller->SetNodeKeywords(OuterNode, InNewText.ToString(), true, false, true);
}
}
}
}
}
FText FControlRigGraphDetails::GetNodeDescription() const
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
return FText::FromString(OuterNode->GetNodeDescription());
}
}
}
return FText();
}
void FControlRigGraphDetails::SetNodeDescription(const FText& InNewText, ETextCommit::Type InCommitType)
{
if(InCommitType == ETextCommit::OnCleared)
{
return;
}
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph()))
{
Controller->SetNodeDescription(OuterNode, InNewText.ToString(), true, false, true);
}
}
}
}
}
FLinearColor FControlRigGraphDetails::GetNodeColor() const
{
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
return OuterNode->GetNodeColor();
}
}
}
return FLinearColor::White;
}
void FControlRigGraphDetails::SetNodeColor(FLinearColor InColor, bool bSetupUndoRedo)
{
TargetColor = InColor;
if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid())
{
UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get();
if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get()))
{
if (URigVMCollapseNode* OuterNode = Cast<URigVMCollapseNode>(Model->GetOuter()))
{
if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph()))
{
Controller->SetNodeColor(OuterNode, TargetColor, bSetupUndoRedo, bIsPickingColor, true);
}
}
}
}
}
void FControlRigGraphDetails::OnNodeColorBegin()
{
bIsPickingColor = true;
}
void FControlRigGraphDetails::OnNodeColorEnd()
{
bIsPickingColor = false;
}
void FControlRigGraphDetails::OnNodeColorCancelled(FLinearColor OriginalColor)
{
SetNodeColor(OriginalColor, true);
}
FReply FControlRigGraphDetails::OnNodeColorClicked()
{
TargetColor = GetNodeColor();
TargetColors.Reset();
TargetColors.Add(&TargetColor);
FColorPickerArgs PickerArgs;
PickerArgs.ParentWidget = ColorBlock;
PickerArgs.bUseAlpha = false;
PickerArgs.DisplayGamma = false;
PickerArgs.InitialColorOverride = TargetColor;
PickerArgs.LinearColorArray = &TargetColors;
PickerArgs.OnInteractivePickBegin = FSimpleDelegate::CreateSP(this, &FControlRigGraphDetails::OnNodeColorBegin);
PickerArgs.OnInteractivePickEnd = FSimpleDelegate::CreateSP(this, &FControlRigGraphDetails::OnNodeColorEnd);
PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateSP(this, &FControlRigGraphDetails::SetNodeColor, true);
PickerArgs.OnColorPickerCancelled = FOnColorPickerCancelled::CreateSP(this, &FControlRigGraphDetails::OnNodeColorCancelled);
OpenColorPicker(PickerArgs);
return FReply::Handled();
}
TArray<TSharedPtr<FString>> FControlRigGraphDetails::AccessSpecifierStrings;
FText FControlRigGraphDetails::GetCurrentAccessSpecifierName() const
{
if(ControlRigBlueprintPtr.IsValid() && GraphPtr.IsValid())
{
UControlRigGraph* Graph = GraphPtr.Get();
UControlRigBlueprint* ControlRigBlueprint = ControlRigBlueprintPtr.Get();
const FControlRigPublicFunctionData ExpectedFunctionData = Graph->GetPublicFunctionData();
if(ControlRigBlueprint->IsFunctionPublic(ExpectedFunctionData.Name))
{
return FText::FromString(*AccessSpecifierStrings[0].Get()); // public
}
}
return FText::FromString(*AccessSpecifierStrings[1].Get()); // private
}
void FControlRigGraphDetails::OnAccessSpecifierSelected( TSharedPtr<FString> SpecifierName, ESelectInfo::Type SelectInfo )
{
if(ControlRigBlueprintPtr.IsValid() && GraphPtr.IsValid())
{
UControlRigGraph* Graph = GraphPtr.Get();
UControlRigBlueprint* ControlRigBlueprint = ControlRigBlueprintPtr.Get();
const FControlRigPublicFunctionData ExpectedFunctionData = Graph->GetPublicFunctionData();
if(SpecifierName->Equals(TEXT("Private")))
{
ControlRigBlueprint->MarkFunctionPublic(ExpectedFunctionData.Name, false);
}
else
{
ControlRigBlueprint->MarkFunctionPublic(ExpectedFunctionData.Name, true);
}
}
}
TSharedRef<ITableRow> FControlRigGraphDetails::HandleGenerateRowAccessSpecifier( TSharedPtr<FString> SpecifierName, const TSharedRef<STableViewBase>& OwnerTable )
{
return SNew(STableRow< TSharedPtr<FString> >, OwnerTable)
.Content()
[
SNew( STextBlock )
.Text(FText::FromString(*SpecifierName.Get()) )
];
}
#if !UE_RIGVM_UCLASS_BASED_STORAGE_DISABLED
TSharedRef<IDetailCustomization> FControlRigWrappedNodeDetails::MakeInstance()
{
return MakeShareable(new FControlRigWrappedNodeDetails);
}
void FControlRigWrappedNodeDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
TArray<TWeakObjectPtr<UObject>> Objects;
DetailLayout.GetObjectsBeingCustomized(Objects);
TArray<UDetailsViewWrapperObject*> Wrappers;
TArray<URigVMNode*> Nodes;
for(const TWeakObjectPtr<UObject>& Object : Objects)
{
UDetailsViewWrapperObject* Wrapper = CastChecked<UDetailsViewWrapperObject>(Object);
Wrappers.Add(Wrapper);
URigVMNode* Node = Wrapper->GetTypedOuter<URigVMNode>();
if(Node == nullptr)
{
return;
}
Nodes.Add(Node);
}
UDetailsViewWrapperObject* FirstWrapper = Wrappers[0];
URigVMNode* FirstNode = Nodes[0];
TArray<FName> Categories;
DetailLayout.GetCategoryNames(Categories);
if(Categories.IsEmpty())
{
return;
}
IDetailCategoryBuilder& DefaultsCategory = DetailLayout.EditCategory(Categories[0]);
UControlRigBlueprint* Blueprint = FirstNode->GetTypedOuter<UControlRigBlueprint>();
if(Blueprint == nullptr)
{
return;
}
for(URigVMPin* Pin : FirstNode->GetPins())
{
bool bHasAnyInputLink = false;
for(URigVMNode* EachNode : Nodes)
{
if(URigVMPin* EachPin = EachNode->FindPin(Pin->GetName()))
{
if(EachPin->GetSourceLinks(true).Num() > 0)
{
bHasAnyInputLink = true;
break;
}
}
}
TSharedPtr<IPropertyHandle> PinHandle = DetailLayout.GetProperty(Pin->GetFName());
if(PinHandle.IsValid())
{
static const TCHAR DisabledFormat[] = TEXT("%s\n\nNote: Editing disabled since pin has an input link.");
if (Pin->IsBoundToVariable())
{
DefaultsCategory.AddCustomRow(FText::FromName(Pin->GetDisplayName()))
.NameContent()
[
PinHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SControlRigVariableBinding)
.ModelPin(Pin)
.Blueprint(Blueprint)
];
}
else
{
DefaultsCategory.AddProperty(PinHandle)
.DisplayName(FText::FromName(Pin->GetDisplayName()))
.IsEnabled(!bHasAnyInputLink)
.ToolTip(FText::FromString(FString::Printf(DisabledFormat, *Pin->GetToolTipText().ToString())));
}
}
}
if(Objects.Num() > 1)
{
return;
}
UControlRig* DebuggedRig = Cast<UControlRig>(Blueprint->GetObjectBeingDebugged());
if(DebuggedRig == nullptr)
{
return;
}
URigVM* VM = DebuggedRig->GetVM();
if(VM == nullptr)
{
return;
}
TSharedPtr<FRigVMParserAST> AST = FirstNode->GetGraph()->GetRuntimeAST(Blueprint->VMCompileSettings.ASTSettings, false);
if(!AST.IsValid())
{
return;
}
FRigVMByteCode& ByteCode = VM->GetByteCode();
if(ByteCode.GetFirstInstructionIndexForSubject(FirstNode) == INDEX_NONE)
{
return;
}
IDetailCategoryBuilder& DebugCategory = DetailLayout.EditCategory("DebugLiveValues", LOCTEXT("DebugLiveValues", "Inspect Live Values"), ECategoryPriority::Uncommon);
DebugCategory.InitiallyCollapsed(true);
for(URigVMPin* Pin : FirstNode->GetPins())
{
// only show hidden pins in debug mode
if(Pin->GetDirection() == ERigVMPinDirection::Hidden)
{
if(!DebuggedRig->IsInDebugMode())
{
continue;
}
}
URigVMPin* SourcePin = Pin;
if(Blueprint->VMCompileSettings.ASTSettings.bFoldAssignments)
{
do
{
TArray<URigVMPin*> SourcePins = SourcePin->GetLinkedSourcePins(false);
if(SourcePins.Num() > 0)
{
SourcePin = SourcePins[0];
}
else
{
break;
}
}
while(SourcePin->GetNode()->IsA<URigVMRerouteNode>());
}
TArray<const FRigVMExprAST*> Expressions = AST->GetExpressionsForSubject(SourcePin);
if(Expressions.Num() == 0 && SourcePin != Pin)
{
SourcePin = Pin;
Expressions = AST->GetExpressionsForSubject(Pin);
}
bool bHasVar = false;
for(const FRigVMExprAST* Expression : Expressions)
{
if(Expression->IsA(FRigVMExprAST::EType::Literal))
{
continue;
}
else if(Expression->IsA(FRigVMExprAST::EType::Var))
{
bHasVar = true;
break;
}
}
TArray<const FRigVMExprAST*> FilteredExpressions;
for(const FRigVMExprAST* Expression : Expressions)
{
if(Expression->IsA(FRigVMExprAST::EType::Literal))
{
if(bHasVar)
{
continue;
}
FilteredExpressions.Add(Expression);
}
else if(Expression->IsA(FRigVMExprAST::EType::Var))
{
FilteredExpressions.Add(Expression);
}
else if(Expression->IsA(FRigVMExprAST::EType::CachedValue))
{
const FRigVMCachedValueExprAST* CachedValueExpr = Expression->To<FRigVMCachedValueExprAST>();
FilteredExpressions.Add(CachedValueExpr->GetVarExpr());
}
}
bool bAddedProperty = false;
int32 SuffixIndex = 1;
FString NameSuffix;
TArray<FRigVMOperand> KnownOperands;
for(const FRigVMExprAST* Expression : FilteredExpressions)
{
const FRigVMVarExprAST* VarExpr = Expression->To<FRigVMVarExprAST>();
FString PinHash = URigVMCompiler::GetPinHash(SourcePin, VarExpr, false);
const FRigVMOperand* Operand = Blueprint->PinToOperandMap.Find(PinHash);
if(Operand)
{
if(Operand->GetRegisterOffset() != INDEX_NONE)
{
continue;
}
if(KnownOperands.Contains(*Operand))
{
continue;
}
const FProperty* Property = nullptr;
TArray<UObject*> ExternalObjects;
if(Operand->GetMemoryType() == ERigVMMemoryType::External)
{
if(!VM->GetExternalVariables().IsValidIndex(Operand->GetRegisterIndex()))
{
continue;
}
ExternalObjects.Add(DebuggedRig);
Property = VM->GetExternalVariables()[Operand->GetRegisterIndex()].Property;
}
else
{
URigVMMemoryStorage* Memory = VM->GetMemoryByType(Operand->GetMemoryType());
if(Memory == nullptr)
{
continue;
}
if(Memory->GetOuter() == GetTransientPackage())
{
continue;
}
// the UClass must be alive for the details view to access it
// this ensure can fail if VM memory is not updated immediately after compile
// because of deferred copy
if(!ensure(IsValidChecked(Memory->GetClass())))
{
continue;
}
if(Memory->GetClass()->GetOuter() == GetTransientPackage())
{
continue;
}
if(!Memory->IsValidIndex(Operand->GetRegisterIndex()))
{
continue;
}
Property = Memory->GetProperty(Operand->GetRegisterIndex());
if(Property == nullptr)
{
continue;
}
ExternalObjects.Add(Memory);
}
check(ExternalObjects.Num() > 0);
check(Property);
IDetailPropertyRow* PropertyRow = DebugCategory.AddExternalObjectProperty(ExternalObjects, Property->GetFName(), EPropertyLocation::Default, FAddPropertyParams().ForceShowProperty());
if(PropertyRow)
{
PropertyRow->DisplayName(FText::FromString(FString::Printf(TEXT("%s%s"), *Pin->GetName(), *NameSuffix)));
PropertyRow->IsEnabled(false);
SuffixIndex++;
bAddedProperty = true;
NameSuffix = FString::Printf(TEXT("_%d"), SuffixIndex);
}
KnownOperands.Add(*Operand);
}
}
if(!bAddedProperty)
{
TSharedPtr<IPropertyHandle> PinHandle = DetailLayout.GetProperty(Pin->GetFName());
if(PinHandle.IsValid())
{
DebugCategory.AddProperty(PinHandle)
.DisplayName(FText::FromName(Pin->GetDisplayName()))
.IsEnabled(false);
}
}
}
}
#endif
#undef LOCTEXT_NAMESPACE