You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#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]
1503 lines
43 KiB
C++
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
|