// Copyright Epic Games, Inc. All Rights Reserved. #include "ControlRigModuleDetails.h" #include "Widgets/SWidget.h" #include "IDetailChildrenBuilder.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "Widgets/Input/SVectorInputBox.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SButton.h" #include "ModularRigController.h" #include "ControlRigBlueprint.h" #include "ControlRigEditorStyle.h" #include "ControlRigElementDetails.h" #include "Graph/ControlRigGraph.h" #include "PropertyCustomizationHelpers.h" #include "PropertyEditorModule.h" #include "SEnumCombo.h" #include "HAL/PlatformApplicationMisc.h" #include "Styling/AppStyle.h" #include "Editor/SModularRigTreeView.h" #include "StructViewerFilter.h" #include "StructViewerModule.h" #include "Features/IModularFeatures.h" #include "IPropertyAccessEditor.h" #include "ModularRigRuleManager.h" #include "PropertyPath.h" #include "ScopedTransaction.h" #include "Editor/SRigHierarchyTreeView.h" #include "Widgets/SRigVMVariantTagWidget.h" #include "Algo/Sort.h" #include "Editor/SRigConnectorTargetWidget.h" #include "AssetRegistry/IAssetRegistry.h" #include "AssetRegistry/AssetRegistryModule.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #define LOCTEXT_NAMESPACE "ControlRigModuleDetails" static const FText ControlRigModuleDetailsMultipleValues = LOCTEXT("MultipleValues", "Multiple Values"); static void RigModuleDetails_GetCustomizedInfo(TSharedRef InStructPropertyHandle, UControlRigBlueprint*& OutBlueprint) { TArray Objects; InStructPropertyHandle->GetOuterObjects(Objects); for (UObject* Object : Objects) { if (Object->IsA()) { OutBlueprint = CastChecked(Object); break; } OutBlueprint = Object->GetTypedOuter(); if(OutBlueprint) { break; } if(const UControlRig* ControlRig = Object->GetTypedOuter()) { OutBlueprint = Cast(ControlRig->GetClass()->ClassGeneratedBy); if(OutBlueprint) { break; } } } if (OutBlueprint == nullptr) { TArray Packages; InStructPropertyHandle->GetOuterPackages(Packages); for (UPackage* Package : Packages) { if (Package == nullptr) { continue; } TArray SubObjects; Package->GetDefaultSubobjects(SubObjects); for (UObject* SubObject : SubObjects) { if (UControlRig* Rig = Cast(SubObject)) { UControlRigBlueprint* Blueprint = Cast(Rig->GetClass()->ClassGeneratedBy); if (Blueprint) { if(Blueprint->GetOutermost() == Package) { OutBlueprint = Blueprint; break; } } } } if (OutBlueprint) { break; } } } } static UControlRigBlueprint* RigModuleDetails_GetBlueprintFromRig(UModularRig* InRig) { if(InRig == nullptr) { return nullptr; } UControlRigBlueprint* Blueprint = InRig->GetTypedOuter(); if(Blueprint == nullptr) { Blueprint = Cast(InRig->GetClass()->ClassGeneratedBy); } return Blueprint; } void FRigModuleInstanceDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { PerModuleInfos.Reset(); TArray> DetailObjects; DetailBuilder.GetObjectsBeingCustomized(DetailObjects); for(TWeakObjectPtr DetailObject : DetailObjects) { if(UControlRig* ModuleInstance = Cast(DetailObject)) { if(const UModularRig* ModularRig = Cast(ModuleInstance->GetOuter())) { if(const FRigModuleInstance* Module = ModularRig->FindModule(ModuleInstance)) { const FName ModuleName = Module->Name; FPerModuleInfo Info; Info.ModuleName = ModuleName; Info.Module = ModularRig->GetHandle(ModuleName); if(!Info.Module.IsValid()) { return; } if(const UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { if(const UModularRig* DefaultModularRig = Cast(Blueprint->GeneratedClass->GetDefaultObject())) { Info.DefaultModule = DefaultModularRig->GetHandle(ModuleName); } } PerModuleInfos.Add(Info); } } } } // don't customize if the if(PerModuleInfos.IsEmpty()) { return; } TArray OriginalCategoryNames; DetailBuilder.GetCategoryNames(OriginalCategoryNames); IDetailCategoryBuilder& GeneralCategory = DetailBuilder.EditCategory(TEXT("General"), LOCTEXT("General", "General")); { static const FText NameTooltip = LOCTEXT("NameTooltip", "The name is used to determine the long name (the full path) and to provide a unique address within the rig."); GeneralCategory.AddCustomRow(FText::FromString(TEXT("Name"))) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Name"))) .Font(IDetailLayoutBuilder::GetDetailFont()) .ToolTipText(NameTooltip) ] .ValueContent() [ SNew(SInlineEditableTextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FRigModuleInstanceDetails::GetName) .OnTextCommitted(this, &FRigModuleInstanceDetails::SetName, DetailBuilder.GetPropertyUtilities()) .ToolTipText(NameTooltip) .OnVerifyTextChanged(this, &FRigModuleInstanceDetails::OnVerifyNameChanged) ]; GeneralCategory.AddCustomRow(FText::FromString(TEXT("RigClass"))) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("RigClass"))) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(true) ] .ValueContent() [ SNew(SButton) .ContentPadding(0.0f) .ButtonStyle(FAppStyle::Get(), "NoBorder") .OnClicked(this, &FRigModuleInstanceDetails::HandleOpenRigModuleAsset) .Cursor(EMouseCursor::Default) .Text(this, &FRigModuleInstanceDetails::GetRigClassPath) ]; TWeakPtr WeakThisPtr = SharedThis(this); GeneralCategory.AddCustomRow(FText::FromString(TEXT("Variant Tags"))) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Variant Tags"))) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(true) ] .ValueContent() [ SNew(SRigVMVariantTagWidget) .Orientation(EOrientation::Orient_Horizontal) .CanAddTags(false) .EnableContextMenu(false) .OnGetTags_Lambda([WeakThisPtr]() -> TArray { TArray Tags; if (TSharedPtr StrongThis = WeakThisPtr.Pin()) { const TArray& Infos = StrongThis->PerModuleInfos; for (int32 InfoIndex=0; InfoIndex(Module->GetRig()->GetClass()->ClassGeneratedBy)) { if (InfoIndex == 0) { Tags = ModuleBlueprint->GetAssetVariant().Tags; } else { const TArray& OtherTags = ModuleBlueprint->GetAssetVariant().Tags; bool bSameArray = Tags.Num() == OtherTags.Num(); if (bSameArray) { for (const FRigVMTag& OtherTag : OtherTags) { if (!Tags.ContainsByPredicate([OtherTag](const FRigVMTag& Tag) { return OtherTag.Name == Tag.Name; })) { return {}; } } } else { return {}; } } } } } } } return Tags; }) ]; } IDetailCategoryBuilder& ConnectionsCategory = DetailBuilder.EditCategory(TEXT("Connections"), LOCTEXT("Connections", "Connections")); { bool bDisplayConnectors = PerModuleInfos.Num() >= 1; if (PerModuleInfos.Num() > 1) { UModularRig* ModularRig = PerModuleInfos[0].GetModularRig(); for (FPerModuleInfo& Info : PerModuleInfos) { if (Info.GetModularRig() != ModularRig) { bDisplayConnectors = false; break; } } } TArray Connectors; TArray> IsArrayConnector; if (bDisplayConnectors) { Connectors = GetConnectors(); // sort connectors primary first, then secondary, then optional Algo::SortBy(Connectors, [](const FRigModuleConnector& Connector) -> int32 { return Connector.IsPrimary() ? 0 : (Connector.IsOptional() ? 2 : 1); }); IsArrayConnector.Reserve(Connectors.Num()); for(const FRigModuleConnector& Connector : Connectors) { const FText Label = FText::FromString(Connector.Name); IsArrayConnector.Emplace(); TOptional& IsArray = IsArrayConnector.Last(); TArray Matches; for (int32 ModuleIndex=0; ModuleIndexGetHierarchy()) { const FRigHierarchyModulePath ConnectorModulePath(Info.ModuleName.ToString(), Connector.Name); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); if (FRigConnectorElement* ConnectorElement = Cast(Hierarchy->Find(ConnectorKey))) { if(ModuleIndex == 0) { IsArray = ConnectorElement->IsArrayConnector(); } else if(IsArray.Get(false) != ConnectorElement->IsArrayConnector()) { IsArray.Reset(); } } } } } } } } if(bDisplayConnectors) { for(int32 ConnectorIndex = 0; ConnectorIndex < Connectors.Num(); ConnectorIndex++) { const FRigModuleConnector& Connector = Connectors[ConnectorIndex]; const FString ConnectorName = Connector.Name; const FText Label = FText::FromString(Connector.Name); bool bIsArrayConnector = IsArrayConnector[ConnectorIndex].Get(false); FRigTreeDelegates RigTreeDelegates; RigTreeDelegates.OnGetHierarchy = FOnGetRigTreeHierarchy::CreateLambda([this]() { return PerModuleInfos[0].GetModularRig()->GetHierarchy(); }); static const FSlateBrush* PrimaryBrush = FControlRigEditorStyle::Get().GetBrush("ControlRig.ConnectorPrimary"); static const FSlateBrush* SecondaryBrush = FControlRigEditorStyle::Get().GetBrush("ControlRig.ConnectorSecondary"); static const FSlateBrush* OptionalBrush = FControlRigEditorStyle::Get().GetBrush("ControlRig.ConnectorOptional"); const FSlateBrush* IconBrush = Connector.IsPrimary() ? PrimaryBrush : (Connector.IsOptional() ? OptionalBrush : SecondaryBrush); TSharedRef ValueContentVerticalBox = SNew(SVerticalBox); TOptional> TargetKeys; if (PerModuleInfos.Num() >= 1) { for (int32 ModuleIndex=0; ModuleIndex CurrentTargets = Blueprint->ModularRigModel.Connections.FindTargetsFromConnector(ConnectorKey); if (!TargetKeys.IsSet()) { TargetKeys = CurrentTargets; } else { const TArray& FlatKeys = TargetKeys.GetValue(); if(FlatKeys.Num() != CurrentTargets.Num()) { TargetKeys.Reset(); break; } for(int32 TargetKeyIndex = 0; TargetKeyIndex < CurrentTargets.Num(); TargetKeyIndex++) { if(FlatKeys[TargetKeyIndex] != CurrentTargets[TargetKeyIndex]) { TargetKeys.Reset(); break; } } if(!TargetKeys.IsSet()) { break; } } } } } const FRigHierarchyModulePath ConnectorModulePath(PerModuleInfos[0].ModuleName.ToString(), Connector.Name); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); FDetailWidgetRow& ConnectorRow = ConnectionsCategory.AddCustomRow(Label) .NameContent() .HAlign(HAlign_Left) .VAlign(VAlign_Top) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(0.f, 0.f, 4.f, 0.f) .HAlign(HAlign_Left) .VAlign(VAlign_Top) [ SNew(SImage) .Image(IconBrush) .ColorAndOpacity(FSlateColor::UseForeground()) .DesiredSizeOverride(FVector2D(16, 16)) ] +SHorizontalBox::Slot() .AutoWidth() .Padding(0.f, 0.f, 0.f, 0.f) .HAlign(HAlign_Left) .VAlign(VAlign_Top) [ SNew(STextBlock) .Text(Label) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(true) ] ]; if(TargetKeys.IsSet()) { TSharedPtr HorizontalBox; ConnectorRow .ValueContent() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SAssignNew(HorizontalBox, SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(0.f, 0.f, 0.f, 0.f) [ SNew(SRigConnectorTargetWidget) .Outer(PerModuleInfos[0].GetBlueprint()) .ConnectorKey(ConnectorKey) .IsArray(bIsArrayConnector) .ExpandArrayByDefault(true) .Targets(TargetKeys.GetValue()) .OnSetTargetArray(FRigConnectorTargetWidget_SetTargetArray::CreateSP(this, &FRigModuleInstanceDetails::OnConnectorTargetChanged, Connector)) .RigTreeDelegates(RigTreeDelegates) ] ]; if(!bIsArrayConnector) { // Reset button HorizontalBox->AddSlot() .AutoWidth() .Padding(4.f, 0.f, 0.f, 0.f) .VAlign(VAlign_Center) [ SAssignNew(ResetConnectorButton.FindOrAdd(Connector.Name), SButton) .ButtonStyle( FAppStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity_Lambda([this, Connector]() { const TSharedPtr& Button = ResetConnectorButton.FindRef(Connector.Name); return Button.IsValid() && Button->IsHovered() ? FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8)) : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.4)); }) .OnClicked_Lambda([this, Connector]() { for (FPerModuleInfo& Info : PerModuleInfos) { const FRigHierarchyModulePath ConnectorModulePath(Info.ModuleName.ToString(), Connector.Name); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); Info.GetBlueprint()->GetModularRigController()->DisconnectConnector(ConnectorKey); } return FReply::Handled(); }) .ContentPadding(1.f) .ToolTipText(NSLOCTEXT("ControlRigModuleDetails", "Reset_Connector", "Reset Connector")) [ SNew(SImage) //.DesiredSizeOverride(FVector2D(16,16)) .ColorAndOpacity_Lambda( [this, Connector]() { const TSharedPtr& Button = ResetConnectorButton.FindRef(Connector.Name); return Button.IsValid() && Button->IsHovered() ? FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8)) : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.4)); }) .Image(FSlateIcon(FAppStyle::Get().GetStyleSetName(), "PropertyWindow.DiffersFromDefault").GetIcon()) ] ]; // Use button HorizontalBox->AddSlot() .AutoWidth() .Padding(0.f, 0.f, 0.f, 0.f) .VAlign(VAlign_Center) [ SAssignNew(UseSelectedButton.FindOrAdd(Connector.Name), SButton) .ButtonStyle( FAppStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity_Lambda([this, Connector]() { const TSharedPtr& Button = UseSelectedButton.FindRef(Connector.Name); return Button.IsValid() && Button->IsHovered() ? FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8)) : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.4)); }) .OnClicked_Lambda([this, Connector]() { if (UModularRig* ModularRig = PerModuleInfos[0].GetModularRig()) { const TArray& Selected = ModularRig->GetHierarchy()->GetSelectedKeys(); if (Selected.Num() > 0) { for (FPerModuleInfo& Info : PerModuleInfos) { const FRigHierarchyModulePath ConnectorModulePath(Info.ModuleName.ToString(), Connector.Name); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); Info.GetBlueprint()->GetModularRigController()->ConnectConnectorToElements(ConnectorKey, Selected, true, ModularRig->GetModularRigSettings().bAutoResolve); } } } return FReply::Handled(); }) .ContentPadding(1.f) .ToolTipText(NSLOCTEXT("ControlRigModuleDetails", "Use_Selected", "Use Selected")) [ SNew(SImage) //.DesiredSizeOverride(FVector2D(16,16)) .ColorAndOpacity_Lambda( [this, Connector]() { const TSharedPtr& Button = UseSelectedButton.FindRef(Connector.Name); return Button.IsValid() && Button->IsHovered() ? FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8)) : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.4)); }) .Image(FAppStyle::GetBrush("Icons.CircleArrowLeft")) ] ]; // Select in hierarchy button HorizontalBox->AddSlot() .AutoWidth() .Padding(0.f, 0.f, 0.f, 0.f) .VAlign(VAlign_Center) [ SAssignNew(SelectElementButton.FindOrAdd(Connector.Name), SButton) .ButtonStyle( FAppStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity_Lambda([this, Connector]() { const TSharedPtr& Button = SelectElementButton.FindRef(Connector.Name); return Button.IsValid() && Button->IsHovered() ? FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8)) : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.4)); }) .OnClicked_Lambda([this, Connector]() { if (UModularRig* ModularRig = PerModuleInfos[0].GetModularRig()) { const FRigHierarchyModulePath ConnectorModulePath(PerModuleInfos[0].ModuleName.ToString(), Connector.Name); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); if (const FRigElementKeyRedirector::FKeyArray* TargetKeys = ModularRig->GetElementKeyRedirector().FindExternalKey(ConnectorKey)) { TArray KeysToSelect; KeysToSelect.Append(*TargetKeys); ModularRig->GetHierarchy()->GetController()->SetSelection(KeysToSelect, true); } } return FReply::Handled(); }) .ContentPadding(1.f) .ToolTipText(NSLOCTEXT("ControlRigModuleDetails", "Select_Element", "Select Element")) [ SNew(SImage) //.DesiredSizeOverride(FVector2D(16,16)) .ColorAndOpacity_Lambda( [this, Connector]() { const TSharedPtr& Button = SelectElementButton.FindRef(Connector.Name); return Button.IsValid() && Button->IsHovered() ? FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.8)) : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.4)); }) .Image(FAppStyle::GetBrush("Icons.Search")) ] ]; } } else { ConnectorRow .ValueContent() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(ControlRigModuleDetailsMultipleValues) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; } } } } for(const FName& OriginalCategoryName : OriginalCategoryNames) { IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(OriginalCategoryName); { IPropertyAccessEditor& PropertyAccessEditor = IModularFeatures::Get().GetModularFeature("PropertyAccessEditor"); TArray> DefaultProperties; Category.GetDefaultProperties(DefaultProperties, true, true); for(const TSharedRef& DefaultProperty : DefaultProperties) { const FProperty* Property = DefaultProperty->GetProperty(); if(Property == nullptr) { DetailBuilder.HideProperty(DefaultProperty); continue; } // skip advanced properties for now const bool bAdvancedDisplay = Property->HasAnyPropertyFlags(CPF_AdvancedDisplay); if(bAdvancedDisplay) { DetailBuilder.HideProperty(DefaultProperty); continue; } // skip non-public properties for now const bool bIsPublic = Property->HasAnyPropertyFlags(CPF_Edit | CPF_EditConst); const bool bIsInstanceEditable = !Property->HasAnyPropertyFlags(CPF_DisableEditOnInstance); if(!bIsPublic || !bIsInstanceEditable) { DetailBuilder.HideProperty(DefaultProperty); continue; } TDelegate OnValueChangedDelegate = TDelegate::CreateSP(this, &FRigModuleInstanceDetails::OnConfigValueChanged); DefaultProperty->SetOnPropertyValueChangedWithData(OnValueChangedDelegate); DefaultProperty->SetOnChildPropertyValueChangedWithData(OnValueChangedDelegate); FPropertyBindingWidgetArgs BindingArgs; BindingArgs.Property = (FProperty*)Property; BindingArgs.CurrentBindingText = TAttribute::CreateLambda([this, Property]() { return GetBindingText(Property); }); BindingArgs.CurrentBindingImage = TAttribute::CreateLambda([this, Property]() { return GetBindingImage(Property); }); BindingArgs.CurrentBindingColor = TAttribute::CreateLambda([this, Property]() { return GetBindingColor(Property); }); BindingArgs.OnCanBindPropertyWithBindingChain.BindLambda([](const FProperty* InProperty, TConstArrayView InBindingChain) -> bool { return true; }); BindingArgs.OnCanBindToClass.BindLambda([](UClass* InClass) -> bool { return false; }); BindingArgs.OnCanRemoveBinding.BindRaw(this, &FRigModuleInstanceDetails::CanRemoveBinding); BindingArgs.OnRemoveBinding.BindSP(this, &FRigModuleInstanceDetails::HandleRemoveBinding); BindingArgs.bGeneratePureBindings = true; BindingArgs.bAllowNewBindings = true; BindingArgs.bAllowArrayElementBindings = false; BindingArgs.bAllowStructMemberBindings = false; BindingArgs.bAllowUObjectFunctions = false; BindingArgs.MenuExtender = MakeShareable(new FExtender); BindingArgs.MenuExtender->AddMenuExtension( "Properties", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateSPLambda(this, [this, Property](FMenuBuilder& MenuBuilder) { FillBindingMenu(MenuBuilder, Property); }) ); TSharedPtr ValueWidget = DefaultProperty->CreatePropertyValueWidgetWithCustomization(DetailBuilder.GetDetailsViewSharedPtr().Get()); const bool bShowChildren = true; Category.AddProperty(DefaultProperty).CustomWidget(bShowChildren) .NameContent() [ DefaultProperty->CreatePropertyNameWidget() ] .ValueContent() [ ValueWidget ? ValueWidget.ToSharedRef() : SNullWidget::NullWidget // todo: if the property is bound / or partially bound // mark the property value widget as disabled / read only. ] .ExtensionContent() [ PropertyAccessEditor.MakePropertyBindingWidget(nullptr, BindingArgs) ]; } } } } FText FRigModuleInstanceDetails::GetName() const { const FRigModuleInstance* FirstModule = PerModuleInfos[0].GetModule(); if(FirstModule == nullptr) { return FText(); } const FName FirstValue = FirstModule->Name; if(PerModuleInfos.Num() > 1) { bool bSame = true; for (int32 i=1; iName.IsEqual(FirstValue, ENameCase::IgnoreCase)) { bSame = false; break; } } } if (!bSame) { return ControlRigModuleDetailsMultipleValues; } } return FText::FromName(FirstValue); } void FRigModuleInstanceDetails::SetName(const FText& InValue, ETextCommit::Type InCommitType, const TSharedRef PropertyUtilities) { if(InValue.IsEmpty()) { return; } for (FPerModuleInfo& Info : PerModuleInfos) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { if (UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { UModularRigController* Controller = Blueprint->GetModularRigController(); const FName OldModuleName = ModuleInstance->Name; (void)Controller->RenameModule(OldModuleName, *InValue.ToString(), true); } } } } bool FRigModuleInstanceDetails::OnVerifyNameChanged(const FText& InText, FText& OutErrorMessage) { if(InText.IsEmpty()) { static const FText EmptyNameIsNotAllowed = LOCTEXT("EmptyNameIsNotAllowed", "Empty name is not allowed."); OutErrorMessage = EmptyNameIsNotAllowed; return false; } for (FPerModuleInfo& Info : PerModuleInfos) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { if (UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { UModularRigController* Controller = Blueprint->GetModularRigController(); if (!Controller->CanRenameModule(ModuleInstance->Name, *InText.ToString(), OutErrorMessage)) { return false; } } } } return true; } FText FRigModuleInstanceDetails::GetRigClassPath() const { if(PerModuleInfos.Num() > 1) { if(const FRigModuleInstance* FirstModule = PerModuleInfos[0].GetModule()) { bool bSame = true; for (int32 i=1; iGetRig()->GetClass() != FirstModule->GetRig()->GetClass()) { bSame = false; break; } } } if (!bSame) { return ControlRigModuleDetailsMultipleValues; } } } if (const FRigModuleInstance* Module = PerModuleInfos[0].GetModule()) { if (const UControlRig* ModuleRig = Module->GetRig()) { return FText::FromString(ModuleRig->GetClass()->GetClassPathName().ToString()); } } return FText(); } FReply FRigModuleInstanceDetails::HandleOpenRigModuleAsset() const { if(PerModuleInfos.Num() == 1) { if (const FRigModuleInstance* Module = PerModuleInfos[0].GetModule()) { if (const UControlRig* ModuleRig = Module->GetRig()) { if(const UObject* Blueprint = ModuleRig->GetClass()->ClassGeneratedBy) { const FSoftObjectPath SoftObjectPath(Blueprint); TArray AssetData; AssetData.Add(FModuleManager::LoadModuleChecked("AssetRegistry").Get().GetAssetByObjectPath(SoftObjectPath.GetWithoutSubPath())); FModuleManager::LoadModuleChecked("ContentBrowser").Get().SyncBrowserToAssets(AssetData); return FReply::Handled(); } } } } return FReply::Unhandled(); } TArray FRigModuleInstanceDetails::GetConnectors() const { if(PerModuleInfos.Num() > 1) { TArray CommonConnectors; if (const FRigModuleInstance* Module = PerModuleInfos[0].GetModule()) { if (const UControlRig* ModuleRig = Module->GetRig()) { CommonConnectors = ModuleRig->GetRigModuleSettings().ExposedConnectors; } } for (int32 ModuleIndex=1; ModuleIndexGetRig()) { const TArray& ModuleConnectors = ModuleRig->GetRigModuleSettings().ExposedConnectors; CommonConnectors = CommonConnectors.FilterByPredicate([ModuleConnectors](const FRigModuleConnector& Connector) { return ModuleConnectors.Contains(Connector); }); } } } return CommonConnectors; } if (const FRigModuleInstance* Module = PerModuleInfos[0].GetModule()) { if (const UControlRig* ModuleRig = Module->GetRig()) { return ModuleRig->GetRigModuleSettings().ExposedConnectors; } } return TArray(); } FRigElementKeyRedirector FRigModuleInstanceDetails::GetConnections() const { if(PerModuleInfos.Num() > 1) { return FRigElementKeyRedirector(); } if (const FRigModuleInstance* Module = PerModuleInfos[0].GetModule()) { if (UControlRig* ModuleRig = Module->GetRig()) { return ModuleRig->GetElementKeyRedirector(); } } return FRigElementKeyRedirector(); } void FRigModuleInstanceDetails::OnConfigValueChanged(const FPropertyChangedEvent& InPropertyChangedEvent) { UControlRigBlueprint* Blueprint = PerModuleInfos[0].GetBlueprint(); if(Blueprint == nullptr) { return; } FString PropertyPathString; { TSharedRef< FPropertyPath > PropertyPath = FPropertyPath::CreateEmpty(); FProperty* Property = InPropertyChangedEvent.Property; if(!ensure(Property)) { return; } TMap PropertyNameStack; InPropertyChangedEvent.GetArrayIndicesPerObject(0, PropertyNameStack); if (PropertyNameStack.Num() > 0) { TArray PropertyNames; PropertyNameStack.GetKeys(PropertyNames); // the property names in the array are provided in reverse order for(int32 Index = PropertyNames.Num() - 1; Index >= 0; Index--) { if(!PropertyPath->IsValid()) { ensure(InPropertyChangedEvent.GetNumObjectsBeingEdited() > 0); Property = FControlRigOverrideValue::FindProperty(InPropertyChangedEvent.GetObjectBeingEdited(0)->GetClass(), PropertyNames[Index]); if(!ensure(Property != nullptr)) { return; } } else { if(const FArrayProperty* ArrayProperty = CastField(Property)) { Property = ArrayProperty->Inner; } const FStructProperty* StructProperty = CastField(Property); if(!ensure(StructProperty != nullptr)) { return; } Property = FControlRigOverrideValue::FindProperty(StructProperty->Struct, PropertyNames[Index]); if(!ensure(Property)) { return; } } PropertyPath->AddProperty( { TWeakFieldPtr(Property), InPropertyChangedEvent.GetArrayIndex(PropertyNames[Index]) } ); } } else { PropertyPath->AddProperty( { TWeakFieldPtr(InPropertyChangedEvent.MemberProperty), InPropertyChangedEvent.GetArrayIndex(InPropertyChangedEvent.GetMemberPropertyName().ToString()) } ); } PropertyPathString = PropertyPath->ToString(); } // we need to shorten the path if we already have data on something above this. bool bFoundValueWithShorterPath = true; while(bFoundValueWithShorterPath) { bFoundValueWithShorterPath = false; for(const FPerModuleInfo& Info : PerModuleInfos) { if (const FRigModuleReference* ModuleReference = Blueprint->ModularRigModel.FindModule(Info.ModuleName)) { for(const FControlRigOverrideValue& Override : ModuleReference->ConfigOverrides) { const FString& ParentPath = Override.GetPath(); if(FControlRigOverrideContainer::IsChildPathOf(PropertyPathString, ParentPath)) { bFoundValueWithShorterPath = true; PropertyPathString = ParentPath; break; } } } if(bFoundValueWithShorterPath) { break; } } } TMap ModuleValues; ModuleValues.Reserve(PerModuleInfos.Num()); for(const FPerModuleInfo& Info : PerModuleInfos) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { if (const UControlRig* ModuleRig = ModuleInstance->GetRig()) { FControlRigOverrideValue ConfigValue = FControlRigOverrideValue(PropertyPathString, ModuleRig); ModuleValues.Add(ModuleInstance->Name, ConfigValue); } } } FScopedTransaction Transaction(LOCTEXT("ModuleHierarchySetConfigValue", "Set Module Config Value")); for (const TPair& Value : ModuleValues) { UModularRigController* Controller = Blueprint->GetModularRigController(); Controller->SetConfigValueInModule(Value.Key, Value.Value); } } bool FRigModuleInstanceDetails::OnConnectorTargetChanged(TArray InTargets, FRigModuleConnector InConnector) { bool bResult = true; FScopedTransaction Transaction(LOCTEXT("ModuleHierarchyResolveConnector", "Resolve Connector")); for (FPerModuleInfo& Info : PerModuleInfos) { if (UModularRigController* Controller = Info.GetBlueprint()->GetModularRigController()) { const FRigHierarchyModulePath ConnectorModulePath(Info.ModuleName.ToString(), InConnector.Name); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); if (!InTargets.IsEmpty()) { const FModularRigSettings& Settings = Info.GetModularRig()->GetModularRigSettings(); if(!Controller->ConnectConnectorToElements(ConnectorKey, InTargets, true, Settings.bAutoResolve)) { bResult = false; } } else { if(!Controller->DisconnectConnector(ConnectorKey)) { bResult = false; } } } } return bResult; } const FRigModuleInstanceDetails::FPerModuleInfo& FRigModuleInstanceDetails::FindModule(const FName& InModuleName) const { const FPerModuleInfo* Info = FindModuleByPredicate([InModuleName](const FPerModuleInfo& Info) { if(const FRigModuleInstance* Module = Info.GetModule()) { return Module->Name == InModuleName; } return false; }); if(Info) { return *Info; } static const FPerModuleInfo EmptyInfo; return EmptyInfo; } const FRigModuleInstanceDetails::FPerModuleInfo* FRigModuleInstanceDetails::FindModuleByPredicate(const TFunction& InPredicate) const { return PerModuleInfos.FindByPredicate(InPredicate); } bool FRigModuleInstanceDetails::ContainsModuleByPredicate(const TFunction& InPredicate) const { return PerModuleInfos.ContainsByPredicate(InPredicate); } void FRigModuleInstanceDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass) { TSharedRef MetadataSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Metadata", LOCTEXT("Metadata", "Metadata")); MetadataSection->AddCategory("Metadata"); } FText FRigModuleInstanceDetails::GetBindingText(const FProperty* InProperty) const { const FName VariableName = InProperty->GetFName(); FText FirstValue; for (int32 ModuleIndex=0; ModuleIndexBindings.Contains(VariableName)) { const FText BindingText = FText::FromString(ModuleReference->Bindings.FindChecked(VariableName)); if(ModuleIndex == 0) { FirstValue = BindingText; } else if(!FirstValue.EqualTo(BindingText)) { return ControlRigModuleDetailsMultipleValues; } } } } return FirstValue; } const FSlateBrush* FRigModuleInstanceDetails::GetBindingImage(const FProperty* InProperty) const { static const FLazyName TypeIcon(TEXT("Kismet.VariableList.TypeIcon")); static const FLazyName ArrayTypeIcon(TEXT("Kismet.VariableList.ArrayTypeIcon")); if(CastField(InProperty)) { return FAppStyle::GetBrush(ArrayTypeIcon); } return FAppStyle::GetBrush(TypeIcon); } FLinearColor FRigModuleInstanceDetails::GetBindingColor(const FProperty* InProperty) const { if(InProperty) { FEdGraphPinType PinType; const UEdGraphSchema_K2* Schema_K2 = GetDefault(); if (Schema_K2->ConvertPropertyToPinType(InProperty, PinType)) { const URigVMEdGraphSchema* Schema = GetDefault(); return Schema->GetPinTypeColor(PinType); } } return FLinearColor::White; } void FRigModuleInstanceDetails::FillBindingMenu(FMenuBuilder& MenuBuilder, const FProperty* InProperty) const { if(PerModuleInfos.IsEmpty()) { return; } UControlRigBlueprint* Blueprint = PerModuleInfos[0].GetBlueprint(); UModularRigController* Controller = Blueprint->GetModularRigController(); TArray CombinedBindings; for(int32 Index = 0; Index < PerModuleInfos.Num(); Index++) { const FPerModuleInfo& Info = PerModuleInfos[Index]; const TArray Bindings = Controller->GetPossibleBindings(Info.ModuleName, InProperty->GetFName()); if(Index == 0) { CombinedBindings = Bindings; } else { // reduce the set of bindings to the overall possible bindings CombinedBindings.RemoveAll([Bindings](const FString& Binding) { return !Bindings.Contains(Binding); }); } } if(CombinedBindings.IsEmpty()) { MenuBuilder.AddMenuEntry( FUIAction(FExecuteAction()), SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0.0f) [ SNew(STextBlock) .Text(LOCTEXT("NoBindingAvailable", "No bindings available for this property.")) .ColorAndOpacity(FLinearColor::White) ] ); return; } // sort lexically CombinedBindings.Sort(); // create a map of all of the variables per menu prefix (the module path the variables belong to) struct FPerMenuData { FString Name; FString ParentMenuPath; TArray SubMenuPaths; TArray Variables; static void SetupMenu( TSharedRef ThisDetails, const FProperty* InProperty, FMenuBuilder& InMenuBuilder, const FString& InMenuPath, TSharedRef> PerMenuData) { FPerMenuData& Data = PerMenuData->FindChecked((InMenuPath)); Data.SubMenuPaths.Sort(); Data.Variables.Sort(); for(const FString& VariablePath : Data.Variables) { FString VariableName = VariablePath; (void)FRigHierarchyModulePath(VariablePath).Split(nullptr, &VariableName); InMenuBuilder.AddMenuEntry( FUIAction(FExecuteAction::CreateLambda([ThisDetails, InProperty, VariablePath]() { ThisDetails->HandleChangeBinding(InProperty, VariablePath); })), SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(1.0f, 0.0f) [ SNew(SImage) .Image(ThisDetails->GetBindingImage(InProperty)) .ColorAndOpacity(ThisDetails->GetBindingColor(InProperty)) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(4.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(VariableName)) .ColorAndOpacity(FLinearColor::White) ] ); } for(const FString& SubMenuPath : Data.SubMenuPaths) { const FPerMenuData& SubMenuData = PerMenuData->FindChecked(SubMenuPath); const FText Label = FText::FromString(SubMenuData.Name); static const FText TooltipFormat = LOCTEXT("BindingMenuTooltipFormat", "Access to all variables of the {0} module"); const FText Tooltip = FText::Format(TooltipFormat, Label); InMenuBuilder.AddSubMenu(Label, Tooltip, FNewMenuDelegate::CreateLambda([ThisDetails, InProperty, SubMenuPath, PerMenuData](FMenuBuilder& SubMenuBuilder) { SetupMenu(ThisDetails, InProperty, SubMenuBuilder, SubMenuPath, PerMenuData); })); } } }; // define the root menu const TSharedRef> PerMenuData = MakeShared>(); PerMenuData->FindOrAdd(FString()); // make sure all levels of the menu are known and we have the variables available for(const FString& BindingPath : CombinedBindings) { FString MenuPath; (void)FRigHierarchyModulePath(BindingPath).Split(&MenuPath, nullptr); FString PreviousMenuPath = MenuPath; FString ParentMenuPath = MenuPath, RemainingPath; while(FRigHierarchyModulePath(ParentMenuPath).Split(&ParentMenuPath, &RemainingPath)) { // scope since the map may change at the end of this block { FPerMenuData& Data = PerMenuData->FindOrAdd(MenuPath); if(Data.Name.IsEmpty()) { Data.Name = RemainingPath; } } PerMenuData->FindOrAdd(ParentMenuPath).SubMenuPaths.AddUnique(PreviousMenuPath); PerMenuData->FindOrAdd(PreviousMenuPath).ParentMenuPath = ParentMenuPath; PerMenuData->FindOrAdd(PreviousMenuPath).Name = RemainingPath; if(!ParentMenuPath.Contains(FRigHierarchyModulePath::ModuleNameSuffix)) { PerMenuData->FindOrAdd(FString()).SubMenuPaths.AddUnique(ParentMenuPath); PerMenuData->FindOrAdd(ParentMenuPath).Name = ParentMenuPath; } PreviousMenuPath = ParentMenuPath; } FPerMenuData& Data = PerMenuData->FindOrAdd(MenuPath); if(Data.Name.IsEmpty()) { Data.Name = MenuPath; } Data.Variables.Add(BindingPath); if(!MenuPath.IsEmpty()) { PerMenuData->FindChecked(Data.ParentMenuPath).SubMenuPaths.AddUnique(MenuPath); } } // build the menu FPerMenuData::SetupMenu(SharedThis(this), InProperty, MenuBuilder, FString(), PerMenuData); } bool FRigModuleInstanceDetails::CanRemoveBinding(FName InPropertyName) const { // offer the "removing binding" button if any of the selected module instances // has a binding for the given variable for(const FPerModuleInfo& Info : PerModuleInfos) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { if(ModuleInstance->VariableBindings.Contains(InPropertyName)) { return true; } } } return false; } void FRigModuleInstanceDetails::HandleRemoveBinding(FName InPropertyName) const { FScopedTransaction Transaction(LOCTEXT("RemoveModuleVariableTransaction", "Remove Binding")); for(const FPerModuleInfo& Info : PerModuleInfos) { if (UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { UModularRigController* Controller = Blueprint->GetModularRigController(); Controller->UnBindModuleVariable(ModuleInstance->Name, InPropertyName); } } } } void FRigModuleInstanceDetails::HandleChangeBinding(const FProperty* InProperty, const FString& InNewVariablePath) const { FScopedTransaction Transaction(LOCTEXT("BindModuleVariableTransaction", "Bind Module Variable")); for(const FPerModuleInfo& Info : PerModuleInfos) { if (UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { UModularRigController* Controller = Blueprint->GetModularRigController(); Controller->BindModuleVariable(ModuleInstance->Name, InProperty->GetFName(), InNewVariablePath); } } } } FReply FRigModuleInstanceDetails::OnAddTargetToArrayConnector(const FString InConnectorName, const TSharedRef PropertyUtilities) { FScopedTransaction Transaction(LOCTEXT("AddTargetToArrayConnector", "Add Target To Array Connector")); for(const FPerModuleInfo& Info : PerModuleInfos) { if (UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { UModularRigController* Controller = Blueprint->GetModularRigController(); const FRigHierarchyModulePath ConnectorModulePath(Info.ModuleName.ToString(), InConnectorName); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); Controller->AddTargetToArrayConnector(ConnectorKey, FRigElementKey(), true, false, false); Blueprint->RecompileModularRig(); } } } PropertyUtilities->RequestForceRefresh(); return FReply::Handled(); } FReply FRigModuleInstanceDetails::OnClearTargetsForArrayConnector(const FString InConnectorName, const TSharedRef PropertyUtilities) { FScopedTransaction Transaction(LOCTEXT("TransactionClearTargetsForArrayConnector", "Clear Targets For Array Connector")); for(const FPerModuleInfo& Info : PerModuleInfos) { if (UControlRigBlueprint* Blueprint = Info.GetBlueprint()) { if (const FRigModuleInstance* ModuleInstance = Info.GetModule()) { UModularRigController* Controller = Blueprint->GetModularRigController(); const FRigHierarchyModulePath ConnectorModulePath(Info.ModuleName.ToString(), InConnectorName); FRigElementKey ConnectorKey(ConnectorModulePath.GetPathFName(), ERigElementType::Connector); Controller->DisconnectConnector(ConnectorKey); } } } PropertyUtilities->RequestForceRefresh(); return FReply::Handled(); } #undef LOCTEXT_NAMESPACE