// 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" #define LOCTEXT_NAMESPACE "ControlRigGraphDetails" FControlRigArgumentGroupLayout::FControlRigArgumentGroupLayout( URigVMGraph* InGraph, UControlRigBlueprint* InBlueprint, bool bInputs) : GraphPtr(InGraph) , ControlRigBlueprintPtr(InBlueprint) , 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(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 ControlRigArgumentLayout = MakeShareable(new FControlRigArgumentLayout( Pin, Graph, ControlRigBlueprintPtr.Get() )); 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(Graph->GetOuter()); if (LibraryNode == nullptr) { return; } switch (InNotifType) { case ERigVMGraphNotifType::PinAdded: case ERigVMGraphNotifType::PinRemoved: case ERigVMGraphNotifType::PinIndexChanged: { URigVMPin* Pin = CastChecked(InSubject); if (Pin->GetNode() == LibraryNode) { OnRebuildChildren.ExecuteIfBound(); } break; } default: { break; } } } void FControlRigArgumentLayout::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) { const UEdGraphSchema* Schema = GetDefault(); ETypeTreeFilter TypeTreeFilter = ETypeTreeFilter::None; TypeTreeFilter |= ETypeTreeFilter::AllowExec; NodeRow .NameContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1) .VAlign(VAlign_Center) [ SAssignNew(ArgumentNameWidget, SEditableTextBox) .Text(this, &FControlRigArgumentLayout::OnGetArgNameText) .OnTextChanged(this, &FControlRigArgumentLayout::OnArgNameChange) .OnTextCommitted(this, &FControlRigArgumentLayout::OnArgNameTextCommitted) .ToolTipText(this, &FControlRigArgumentLayout::OnGetArgToolTipText) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(!ShouldPinBeReadOnly()) ] ] .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::GetVariableTypeTree)) .TargetPinType(this, &FControlRigArgumentLayout::OnGetPinInfo) .OnPinTypePreChanged(this, &FControlRigArgumentLayout::OnPrePinInfoChange) .OnPinTypeChanged(this, &FControlRigArgumentLayout::PinInfoChanged) .Schema(Schema) .TypeTreeFilter(TypeTreeFilter) .bAllowArrays(!ShouldPinBeReadOnly()) .IsEnabled(!ShouldPinBeReadOnly(true)) .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(Pin->GetNode())) { if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph())) { Controller->RemoveExposedPin(Pin->GetFName(), true); } } } } FReply FControlRigArgumentLayout::OnArgMoveUp() { if (PinPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { URigVMPin* Pin = PinPtr.Get(); UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMLibraryNode* LibraryNode = Cast(Pin->GetNode())) { if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph())) { Controller->SetExposedPinIndex(Pin->GetFName(), Pin->GetPinIndex() - 1); 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(Pin->GetNode())) { if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph())) { Controller->SetExposedPinIndex(Pin->GetFName(), Pin->GetPinIndex() + 1); 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::OnArgNameChange(const FText& InNewText) { // do we need validation? } 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(Pin->GetNode())) { if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph())) { const FString& NewName = NewText.ToString(); Controller->RenameExposedPin(Pin->GetFName(), *NewName, 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(Pin->GetNode())) { if (URigVMController* Controller = Blueprint->GetController(LibraryNode->GetContainedGraph())) { FRigVMExternalVariable ExternalVariable = UControlRig::GetExternalVariableFromPinType(Pin->GetFName(), PinType, true, false); if (!ExternalVariable.IsValid(true /* allow nullptr memory */)) { return; } FString CPPType = ExternalVariable.TypeName.ToString(); FName CPPTypeObjectName = NAME_None; if (ExternalVariable.TypeObject) { CPPTypeObjectName = *ExternalVariable.TypeObject->GetPathName(); if (UScriptStruct* ScriptStruct = Cast(ExternalVariable.TypeObject)) { CPPType = ScriptStruct->GetStructCPPName(); } } if (ExternalVariable.bIsArray) { CPPType = FString::Printf(TEXT("TArray<%s>"), *CPPType); } bool bSetupUndoRedo = true; Controller->ChangeExposedPinType(Pin->GetFName(), CPPType, CPPTypeObjectName, bSetupUndoRedo); // 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(GraphPtr->GetOuter())) { if (UControlRigGraph* RigGraph = Cast(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(GraphPtr->GetOuter())) { if (UControlRigGraph* RigGraph = Cast(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(Graph->GetOuter())) { if (UControlRigGraph* RigGraph = Cast(Blueprint->GetEdGraph(LibraryNode->GetGraph()))) { ControlRigGraphNode = Cast(RigGraph->FindNodeForModelNodeName(LibraryNode->GetFName())); } } if (ControlRigGraphNode == nullptr) { return; } ChildrenBuilder.AddCustomRow(FText::GetEmpty()) .WholeRowContent() .MaxDesiredWidth(980.f) [ SAssignNew(OwnedNodeWidget, SControlRigGraphNode).GraphNodeObj(ControlRigGraphNode) ]; } 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(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(InSubject); if (Pin->GetNode() == LibraryNode) { OnRebuildChildren.ExecuteIfBound(); } break; } case ERigVMGraphNotifType::NodeRenamed: case ERigVMGraphNotifType::NodeColorChanged: { URigVMNode* Node = CastChecked(InSubject); if (Node == LibraryNode) { OnRebuildChildren.ExecuteIfBound(); } break; } default: { break; } } } TSharedPtr FControlRigGraphDetails::MakeInstance(TSharedPtr InBlueprintEditor) { const TArray* Objects = (InBlueprintEditor.IsValid() ? InBlueprintEditor->GetObjectsCurrentlyBeingEdited() : nullptr); if (Objects && Objects->Num() == 1) { if (UControlRigBlueprint* ControlRigBlueprint = Cast((*Objects)[0])) { return MakeShareable(new FControlRigGraphDetails(StaticCastSharedPtr(InBlueprintEditor), ControlRigBlueprint)); } } return nullptr; } void FControlRigGraphDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { bIsPickingColor = false; TArray> Objects; DetailLayout.GetObjectsBeingCustomized(Objects); GraphPtr = CastChecked(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 InputArgumentGroup = MakeShareable(new FControlRigArgumentGroupLayout( Model, Blueprint, true)); InputsCategory.AddCustomBuilder(InputArgumentGroup); TSharedRef 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(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 OutputArgumentGroup = MakeShareable(new FControlRigArgumentGroupLayout( Model, Blueprint, false)); OutputsCategory.AddCustomBuilder(OutputArgumentGroup); TSharedRef 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(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(Model->GetOuter())) { bIsFunction = LibraryNode->GetGraph()->IsA(); } } 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.IsEmpty()) { OutErrorMessage = LOCTEXT("CategoryEmpty", "Cannot add a category with an empty string."); 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(new FString(TEXT("Public")))); AccessSpecifierStrings.Add(TSharedPtr(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 >) .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 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(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); } } 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); } } 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(Model->GetOuter())) { return FText::FromString(OuterNode->GetNodeCategory()); } } } return FText(); } void FControlRigGraphDetails::SetNodeCategory(const FText& InNewText, ETextCommit::Type InCommitType) { if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get())) { if (URigVMCollapseNode* OuterNode = Cast(Model->GetOuter())) { if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph())) { Controller->SetNodeCategory(OuterNode, InNewText.ToString()); } } } } } FText FControlRigGraphDetails::GetNodeKeywords() const { if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get())) { if (URigVMCollapseNode* OuterNode = Cast(Model->GetOuter())) { return FText::FromString(OuterNode->GetNodeKeywords()); } } } return FText(); } void FControlRigGraphDetails::SetNodeKeywords(const FText& InNewText, ETextCommit::Type InCommitType) { if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get())) { if (URigVMCollapseNode* OuterNode = Cast(Model->GetOuter())) { if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph())) { Controller->SetNodeKeywords(OuterNode, InNewText.ToString()); } } } } } FText FControlRigGraphDetails::GetNodeDescription() const { if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get())) { if (URigVMCollapseNode* OuterNode = Cast(Model->GetOuter())) { return FText::FromString(OuterNode->GetNodeDescription()); } } } return FText(); } void FControlRigGraphDetails::SetNodeDescription(const FText& InNewText, ETextCommit::Type InCommitType) { if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get())) { if (URigVMCollapseNode* OuterNode = Cast(Model->GetOuter())) { if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph())) { Controller->SetNodeDescription(OuterNode, InNewText.ToString()); } } } } } FLinearColor FControlRigGraphDetails::GetNodeColor() const { if (GraphPtr.IsValid() && ControlRigBlueprintPtr.IsValid()) { UControlRigBlueprint* Blueprint = ControlRigBlueprintPtr.Get(); if (URigVMGraph* Model = Blueprint->GetModel(GraphPtr.Get())) { if (URigVMCollapseNode* OuterNode = Cast(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(Model->GetOuter())) { if (URigVMController* Controller = Blueprint->GetOrCreateController(OuterNode->GetGraph())) { Controller->SetNodeColor(OuterNode, TargetColor, bSetupUndoRedo, bIsPickingColor); } } } } } 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> 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 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 FControlRigGraphDetails::HandleGenerateRowAccessSpecifier( TSharedPtr SpecifierName, const TSharedRef& OwnerTable ) { return SNew(STableRow< TSharedPtr >, OwnerTable) .Content() [ SNew( STextBlock ) .Text(FText::FromString(*SpecifierName.Get()) ) ]; } #undef LOCTEXT_NAMESPACE