// Copyright Epic Games, Inc. All Rights Reserved. #include "ControlRigElementDetails.h" #include "Widgets/SWidget.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "DetailWidgetRow.h" #include "IDetailChildrenBuilder.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SVectorInputBox.h" #include "Widgets/Input/SRotatorInputBox.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SButton.h" #include "Editor/SControlRigGizmoNameList.h" #include "ControlRigBlueprint.h" #include "IDetailGroup.h" #include "Graph/ControlRigGraph.h" #include "PropertyCustomizationHelpers.h" #include "SEnumCombo.h" #include "ControlRig/Private/Units/Execution/RigUnit_BeginExecution.h" #include "RigVMModel/RigVMGraph.h" #include "RigVMModel/RigVMNode.h" #include "Graph/SControlRigGraphPinVariableBinding.h" #include "HAL/PlatformApplicationMisc.h" #define LOCTEXT_NAMESPACE "ControlRigElementDetails" static const FText ControlRigDetailsMultipleValues = LOCTEXT("MultipleValues", "Multiple Values"); namespace FRigElementKeyDetailsDefs { // Active foreground pin alpha static const float ActivePinForegroundAlpha = 1.f; // InActive foreground pin alpha static const float InactivePinForegroundAlpha = 0.15f; // Active background pin alpha static const float ActivePinBackgroundAlpha = 0.8f; // InActive background pin alpha static const float InactivePinBackgroundAlpha = 0.4f; }; void RigElementDetails_GetCustomizedInfo(TSharedRef InStructPropertyHandle, UControlRigBlueprint*& OutBlueprint) { TArray Objects; InStructPropertyHandle->GetOuterObjects(Objects); for (UObject* Object : Objects) { if (Object->IsA()) { OutBlueprint = Cast(Object); 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; } } } } UControlRigBlueprint* RigElementDetails_GetBlueprintFromHierarchy(URigHierarchy* InHierarchy) { if(InHierarchy == nullptr) { return nullptr; } UControlRigBlueprint* Blueprint = InHierarchy->GetTypedOuter(); if(Blueprint == nullptr) { UControlRig* Rig = InHierarchy->GetTypedOuter(); if(Rig) { Blueprint = Cast(Rig->GetClass()->ClassGeneratedBy); } } return Blueprint; } void FRigElementKeyDetails::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { BlueprintBeingCustomized = nullptr; RigElementDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized); UControlRigGraph* RigGraph = nullptr; if(BlueprintBeingCustomized) { for (UEdGraph* Graph : BlueprintBeingCustomized->UbergraphPages) { RigGraph = Cast(Graph); if (RigGraph) { break; } } } // only allow blueprints with at least one rig graph if (RigGraph == nullptr) { BlueprintBeingCustomized = nullptr; } if (BlueprintBeingCustomized == nullptr) { HeaderRow .NameContent() [ InStructPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() [ InStructPropertyHandle->CreatePropertyValueWidget() ]; } else { TypeHandle = InStructPropertyHandle->GetChildHandle(TEXT("Type")); NameHandle = InStructPropertyHandle->GetChildHandle(TEXT("Name")); TypeHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda( [this]() { this->UpdateElementNameList(); SetElementName(FString()); } )); UpdateElementNameList(); HeaderRow .NameContent() [ InStructPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(250.f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ TypeHandle->CreatePropertyValueWidget() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(4.f, 0.f, 0.f, 0.f) [ SAssignNew(SearchableComboBox, SSearchableComboBox) .OptionsSource(&ElementNameList) .OnSelectionChanged(this, &FRigElementKeyDetails::OnElementNameChanged) .OnGenerateWidget(this, &FRigElementKeyDetails::OnGetElementNameWidget) .IsEnabled(!NameHandle->IsEditConst()) .Content() [ SNew(STextBlock) .Text(this, &FRigElementKeyDetails::GetElementNameAsText) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] // Use button +SHorizontalBox::Slot() .AutoWidth() .Padding(1,0) .VAlign(VAlign_Center) [ SAssignNew(UseSelectedButton, SButton) .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity_Lambda([this]() { return OnGetWidgetBackground(UseSelectedButton); }) .OnClicked(this, &FRigElementKeyDetails::OnGetSelectedClicked) .ContentPadding(1.f) .ToolTipText(NSLOCTEXT("GraphEditor", "ObjectGraphPin_Use_Tooltip", "Use item selected")) [ SNew(SImage) .ColorAndOpacity_Lambda( [this]() { return OnGetWidgetForeground(UseSelectedButton); }) .Image(FEditorStyle::GetBrush("Icons.CircleArrowLeft")) ] ] // Select in hierarchy button +SHorizontalBox::Slot() .AutoWidth() .Padding(1,0) .VAlign(VAlign_Center) [ SAssignNew(SelectElementButton, SButton) .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) .ButtonColorAndOpacity_Lambda([this]() { return OnGetWidgetBackground(SelectElementButton); }) .OnClicked(this, &FRigElementKeyDetails::OnSelectInHierarchyClicked) .ContentPadding(0) .ToolTipText(NSLOCTEXT("GraphEditor", "ObjectGraphPin_Browse_Tooltip", "Select in hierarchy")) [ SNew(SImage) .ColorAndOpacity_Lambda( [this]() { return OnGetWidgetForeground(SelectElementButton); }) .Image(FEditorStyle::GetBrush("Icons.Search")) ] ] ]; } } void FRigElementKeyDetails::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { if (InStructPropertyHandle->IsValidHandle()) { // only fill the children if the blueprint cannot be found if (BlueprintBeingCustomized == nullptr) { uint32 NumChildren = 0; InStructPropertyHandle->GetNumChildren(NumChildren); for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++) { StructBuilder.AddProperty(InStructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef()); } } } } ERigElementType FRigElementKeyDetails::GetElementType() const { ERigElementType ElementType = ERigElementType::None; if (TypeHandle.IsValid()) { uint8 Index = 0; TypeHandle->GetValue(Index); ElementType = (ERigElementType)Index; } return ElementType; } FString FRigElementKeyDetails::GetElementName() const { FString ElementNameStr; if (NameHandle.IsValid()) { for(int32 ObjectIndex = 0; ObjectIndex < NameHandle->GetNumPerObjectValues(); ObjectIndex++) { FString PerObjectValue; NameHandle->GetPerObjectValue(ObjectIndex, PerObjectValue); if(ObjectIndex == 0) { ElementNameStr = PerObjectValue; } else if(ElementNameStr != PerObjectValue) { return ControlRigDetailsMultipleValues.ToString(); } } } return ElementNameStr; } void FRigElementKeyDetails::SetElementName(FString InName) { if (NameHandle.IsValid()) { NameHandle->SetValue(InName); } } void FRigElementKeyDetails::UpdateElementNameList() { if (!TypeHandle.IsValid()) { return; } ElementNameList.Reset(); if (BlueprintBeingCustomized) { for (UEdGraph* Graph : BlueprintBeingCustomized->UbergraphPages) { if (UControlRigGraph* RigGraph = Cast(Graph)) { ElementNameList = *RigGraph->GetElementNameList(GetElementType()); if(SearchableComboBox.IsValid()) { SearchableComboBox->RefreshOptions(); } return; } } } } void FRigElementKeyDetails::OnElementNameChanged(TSharedPtr InItem, ESelectInfo::Type InSelectionInfo) { if (InItem.IsValid()) { SetElementName(*InItem); } else { SetElementName(FString()); } } TSharedRef FRigElementKeyDetails::OnGetElementNameWidget(TSharedPtr InItem) { return SNew(STextBlock) .Text(FText::FromString(InItem.IsValid() ? *InItem : FString())) .Font(IDetailLayoutBuilder::GetDetailFont()); } FText FRigElementKeyDetails::GetElementNameAsText() const { return FText::FromString(GetElementName()); } FSlateColor FRigElementKeyDetails::OnGetWidgetForeground(const TSharedPtr Button) const { float Alpha = (Button.IsValid() && Button->IsHovered()) ? FRigElementKeyDetailsDefs::ActivePinForegroundAlpha : FRigElementKeyDetailsDefs::InactivePinForegroundAlpha; return FSlateColor(FLinearColor(1.f, 1.f, 1.f, Alpha)); } FSlateColor FRigElementKeyDetails::OnGetWidgetBackground(const TSharedPtr Button) const { float Alpha = (Button.IsValid() && Button->IsHovered()) ? FRigElementKeyDetailsDefs::ActivePinBackgroundAlpha : FRigElementKeyDetailsDefs::InactivePinBackgroundAlpha; return FSlateColor(FLinearColor(1.f, 1.f, 1.f, Alpha)); } FReply FRigElementKeyDetails::OnGetSelectedClicked() { if (BlueprintBeingCustomized) { const TArray& Selected = BlueprintBeingCustomized->Hierarchy->GetSelectedKeys(); if (Selected.Num() > 0) { if (TypeHandle.IsValid()) { uint8 Index = (uint8) Selected[0].Type; TypeHandle->SetValue(Index); } SetElementName(Selected[0].Name.ToString()); } } return FReply::Handled(); } FReply FRigElementKeyDetails::OnSelectInHierarchyClicked() { if (BlueprintBeingCustomized) { FRigElementKey Key; if (TypeHandle.IsValid()) { uint8 Type; TypeHandle->GetValue(Type); Key.Type = (ERigElementType) Type; } if (NameHandle.IsValid()) { NameHandle->GetValue(Key.Name); } if (Key.IsValid()) { BlueprintBeingCustomized->GetHierarchyController()->SetSelection({Key}); } } return FReply::Handled(); } void FRigUnitDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { TArray> StructsBeingCustomized; DetailBuilder.GetStructsBeingCustomized(StructsBeingCustomized); if (StructsBeingCustomized.Num() == 0) { return; } TSharedPtr StructBeingCustomized = StructsBeingCustomized[0]; BlueprintBeingCustomized = nullptr; if (UPackage* Package = StructBeingCustomized->GetPackage()) { TArray SubObjects; Package->GetDefaultSubobjects(SubObjects); for (UObject* SubObject : SubObjects) { if (UControlRig* Rig = Cast(SubObject)) { BlueprintBeingCustomized = Cast(Rig->GetClass()->ClassGeneratedBy); if (BlueprintBeingCustomized) { break; } } } } if (BlueprintBeingCustomized == nullptr) { return; } GraphBeingCustomized = nullptr; for (UEdGraph* Graph : BlueprintBeingCustomized->UbergraphPages) { GraphBeingCustomized = Cast(Graph); if (GraphBeingCustomized) { break; } } if (GraphBeingCustomized == nullptr) { return; } URigVMGraph* Model = GraphBeingCustomized->GetModel(); if(Model == nullptr) { return; } const TArray SelectedNodeNames = Model->GetSelectNodes(); if(SelectedNodeNames.Num() == 0) { return; } URigVMNode* ModelNode = Model->FindNodeByName(SelectedNodeNames[0]); if(ModelNode == nullptr) { return; } UScriptStruct* ScriptStruct = Cast((UStruct*)StructBeingCustomized->GetStruct()); check(ScriptStruct); IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(*ScriptStruct->GetDisplayNameText().ToString()); for (TFieldIterator PropertyIt(ScriptStruct); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; TSharedPtr PropertyHandle = DetailBuilder.GetProperty(Property->GetFName(), ScriptStruct); if (!PropertyHandle->IsValidHandle()) { continue; } DetailBuilder.HideProperty(PropertyHandle); URigVMPin* ModelPin = ModelNode->FindPin(Property->GetName()); if(ModelPin == nullptr) { continue; } if(ModelPin->IsBoundToVariable()) { CategoryBuilder.AddCustomRow(FText::FromString(Property->GetName())) .NameContent() [ PropertyHandle->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SControlRigVariableBinding) .ModelPin(ModelPin) .Blueprint(BlueprintBeingCustomized) ]; continue; } if (FNameProperty* NameProperty = CastField(Property)) { FString CustomWidgetName = NameProperty->GetMetaData(TEXT("CustomWidget")); if (!CustomWidgetName.IsEmpty()) { const TArray>* NameList = nullptr; if (CustomWidgetName == TEXT("BoneName")) { NameList = GraphBeingCustomized->GetBoneNameList(); } else if (CustomWidgetName == TEXT("ControlName")) { NameList = GraphBeingCustomized->GetControlNameList(); } else if (CustomWidgetName == TEXT("SpaceName")) { NameList = GraphBeingCustomized->GetNullNameList(); } else if (CustomWidgetName == TEXT("CurveName")) { NameList = GraphBeingCustomized->GetCurveNameList(); } if (NameList) { TSharedPtr NameListWidget; CategoryBuilder.AddCustomRow(FText::FromString(Property->GetName())) .NameContent() [ PropertyHandle->CreatePropertyNameWidget() ] .ValueContent() [ SAssignNew(NameListWidget, SControlRigGraphPinNameListValueWidget) .OptionsSource(NameList) .OnGenerateWidget(this, &FRigUnitDetails::MakeNameListItemWidget) .OnSelectionChanged(this, &FRigUnitDetails::OnNameListChanged, StructBeingCustomized, NameProperty, DetailBuilder.GetPropertyUtilities()) .OnComboBoxOpening(this, &FRigUnitDetails::OnNameListComboBox, StructBeingCustomized, NameProperty, NameList) .InitiallySelectedItem(GetCurrentlySelectedItem(StructBeingCustomized, NameProperty, NameList)) .Content() [ SNew(STextBlock) .Text(this, &FRigUnitDetails::GetNameListText, StructBeingCustomized, NameProperty) ] ]; NameListWidgets.Add(Property->GetFName(), NameListWidget); } else { CategoryBuilder.AddCustomRow(FText::FromString(Property->GetName())) .NameContent() [ PropertyHandle->CreatePropertyNameWidget() ]; } continue; } } else if (FStructProperty* StructProperty = CastField(Property)) { const FSimpleDelegate OnStructContentsChangedDelegate = FSimpleDelegate::CreateSP(this, &FRigUnitDetails::OnStructContentsChanged, Property, DetailBuilder.GetPropertyUtilities()); PropertyHandle->SetOnPropertyValueChanged(OnStructContentsChangedDelegate); PropertyHandle->SetOnChildPropertyValueChanged(OnStructContentsChangedDelegate); } CategoryBuilder.AddProperty(PropertyHandle); } } TSharedRef FRigUnitDetails::MakeNameListItemWidget(TSharedPtr InItem) { return SNew(STextBlock).Text(FText::FromString(*InItem));// .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); } FText FRigUnitDetails::GetNameListText(TSharedPtr InStructOnScope, FNameProperty* InProperty) const { if (FName* Value = InProperty->ContainerPtrToValuePtr(InStructOnScope->GetStructMemory())) { return FText::FromName(*Value); } return FText(); } TSharedPtr FRigUnitDetails::GetCurrentlySelectedItem(TSharedPtr InStructOnScope, FNameProperty* InProperty, const TArray>* InNameList) const { FString CurrentItem = GetNameListText(InStructOnScope, InProperty).ToString(); for (const TSharedPtr& Item : *InNameList) { if (Item->Equals(CurrentItem)) { return Item; } } return TSharedPtr(); } void FRigUnitDetails::SetNameListText(const FText& NewTypeInValue, ETextCommit::Type /*CommitInfo*/, TSharedPtr InStructOnScope, FNameProperty* InProperty, TSharedRef PropertyUtilities) { if (FName* Value = InProperty->ContainerPtrToValuePtr(InStructOnScope->GetStructMemory())) { *Value = *NewTypeInValue.ToString(); FPropertyChangedEvent ChangeEvent(InProperty, EPropertyChangeType::ValueSet); PropertyUtilities->NotifyFinishedChangingProperties(ChangeEvent); } } void FRigUnitDetails::OnNameListChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo, TSharedPtr InStructOnScope, FNameProperty* InProperty, TSharedRef PropertyUtilities) { if (SelectInfo != ESelectInfo::Direct) { FString NewValue = *NewSelection.Get(); SetNameListText(FText::FromString(NewValue), ETextCommit::OnEnter, InStructOnScope, InProperty, PropertyUtilities); } } void FRigUnitDetails::OnNameListComboBox(TSharedPtr InStructOnScope, FNameProperty* InProperty, const TArray>* InNameList) { TSharedPtr Widget = NameListWidgets.FindChecked(InProperty->GetFName()); const TSharedPtr CurrentlySelected = GetCurrentlySelectedItem(InStructOnScope, InProperty, InNameList); Widget->SetSelectedItem(CurrentlySelected); } void FRigUnitDetails::OnStructContentsChanged(FProperty* InProperty, const TSharedRef PropertyUtilities) { const FPropertyChangedEvent ChangeEvent(InProperty, EPropertyChangeType::ValueSet); PropertyUtilities->NotifyFinishedChangingProperties(ChangeEvent); } void FRigComputedTransformDetails::CustomizeHeader(TSharedRef InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { BlueprintBeingCustomized = nullptr; RigElementDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized); } void FRigComputedTransformDetails::CustomizeChildren(TSharedRef InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { TransformHandle = InStructPropertyHandle->GetChildHandle(TEXT("Transform")); StructBuilder .AddProperty(TransformHandle.ToSharedRef()) .DisplayName(InStructPropertyHandle->GetPropertyDisplayName()); FString PropertyPath = TransformHandle->GeneratePathToProperty(); if(PropertyPath.StartsWith(TEXT("Struct."))) { PropertyPath.RightChopInline(7); } if(PropertyPath.StartsWith(TEXT("Pose."))) { PropertyPath.RightChopInline(5); PropertyChain.AddTail(FRigTransformElement::StaticStruct()->FindPropertyByName(TEXT("Pose"))); } else if(PropertyPath.StartsWith(TEXT("Offset."))) { PropertyPath.RightChopInline(7); PropertyChain.AddTail(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Offset"))); } else if(PropertyPath.StartsWith(TEXT("Shape."))) { PropertyPath.RightChopInline(6); PropertyChain.AddTail(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Shape"))); } if(PropertyPath.StartsWith(TEXT("Current."))) { PropertyPath.RightChopInline(8); PropertyChain.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Current"))); } else if(PropertyPath.StartsWith(TEXT("Initial."))) { PropertyPath.RightChopInline(8); PropertyChain.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Initial"))); } if(PropertyPath.StartsWith(TEXT("Local."))) { PropertyPath.RightChopInline(6); PropertyChain.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Local"))); } else if(PropertyPath.StartsWith(TEXT("Global."))) { PropertyPath.RightChopInline(7); PropertyChain.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Global"))); } PropertyChain.AddTail(TransformHandle->GetProperty()); PropertyChain.SetActiveMemberPropertyNode(PropertyChain.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateSP(this, &FRigComputedTransformDetails::OnTransformChanged, &PropertyChain); TransformHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); TransformHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); } void FRigComputedTransformDetails::OnTransformChanged(FEditPropertyChain* InPropertyChain) { if(BlueprintBeingCustomized && InPropertyChain) { if(InPropertyChain->Num() > 1) { FPropertyChangedEvent ChangeEvent(InPropertyChain->GetHead()->GetValue(), EPropertyChangeType::ValueSet); ChangeEvent.SetActiveMemberProperty(InPropertyChain->GetTail()->GetValue()); FPropertyChangedChainEvent ChainEvent(*InPropertyChain, ChangeEvent); BlueprintBeingCustomized->BroadcastPostEditChangeChainProperty(ChainEvent); } } } void FRigBaseElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { BlueprintBeingCustomized = nullptr; HierarchyBeingCustomized = nullptr; ObjectsBeingCustomized.Reset(); TArray> DetailObjects; DetailBuilder.GetObjectsBeingCustomized(DetailObjects); for(TWeakObjectPtr DetailObject : DetailObjects) { UDetailsViewWrapperObject* WrapperObject = CastChecked(DetailObject.Get()); if(HierarchyBeingCustomized == nullptr) { HierarchyBeingCustomized = Cast(WrapperObject->GetOuter()); } ObjectsBeingCustomized.Add(WrapperObject); } if(HierarchyBeingCustomized) { BlueprintBeingCustomized = HierarchyBeingCustomized->GetTypedOuter(); if(BlueprintBeingCustomized == nullptr) { if(UControlRig* ControlRig = HierarchyBeingCustomized->GetTypedOuter()) { BlueprintBeingCustomized = Cast(ControlRig->GetClass()->ClassGeneratedBy); } } } if(BlueprintBeingCustomized == nullptr) { RigElementDetails_GetCustomizedInfo(DetailBuilder.GetProperty(TEXT("Key")), BlueprintBeingCustomized); } IDetailCategoryBuilder& GeneralCategory = DetailBuilder.EditCategory(TEXT("General"), LOCTEXT("General", "General")); GeneralCategory.AddCustomRow(FText::FromString(TEXT("Name"))) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Name"))) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SEditableTextBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FRigBaseElementDetails::GetName) .OnTextCommitted(this, &FRigBaseElementDetails::SetName) .OnVerifyTextChanged(this, &FRigBaseElementDetails::OnVerifyNameChanged) .IsEnabled(ObjectsBeingCustomized.Num() == 1) ]; DetailBuilder.HideCategory(TEXT("RigElement")); } FRigElementKey FRigBaseElementDetails::GetElementKey() const { check(ObjectsBeingCustomized.Num() == 1); if(ObjectsBeingCustomized[0].IsValid()) { return ObjectsBeingCustomized[0]->GetContent().GetKey(); } return FRigElementKey(); } FText FRigBaseElementDetails::GetName() const { if(ObjectsBeingCustomized.Num() > 1) { return ControlRigDetailsMultipleValues; } return FText::FromName(GetElementKey().Name); } void FRigBaseElementDetails::SetName(const FText& InNewText, ETextCommit::Type InCommitType) { if(InCommitType == ETextCommit::OnCleared) { return; } if(ObjectsBeingCustomized.Num() > 1) { return; } URigHierarchy* Hierarchy = nullptr; if (BlueprintBeingCustomized) { Hierarchy = BlueprintBeingCustomized->Hierarchy; } else { Hierarchy = GetHierarchy(); } if (Hierarchy) { URigHierarchyController* Controller = Hierarchy->GetController(true); check(Controller); Controller->RenameElement(GetElementKey(), *InNewText.ToString(), true, true); } } bool FRigBaseElementDetails::OnVerifyNameChanged(const FText& InText, FText& OutErrorMessage) { if(ObjectsBeingCustomized.Num() > 1) { return false; } URigHierarchy* Hierarchy = nullptr; if (BlueprintBeingCustomized) { Hierarchy = BlueprintBeingCustomized->Hierarchy; } else { Hierarchy = GetHierarchy(); } if (!Hierarchy) { return false; } if (GetElementKey().Name.ToString() == InText.ToString()) { return true; } FString OutErrorMessageStr; if (!Hierarchy->IsNameAvailable(InText.ToString(), GetElementKey().Type, &OutErrorMessageStr)) { OutErrorMessage = FText::FromString(OutErrorMessageStr); return false; } return true; } void FRigBaseElementDetails::OnStructContentsChanged(FProperty* InProperty, const TSharedRef PropertyUtilities) { const FPropertyChangedEvent ChangeEvent(InProperty, EPropertyChangeType::ValueSet); PropertyUtilities->NotifyFinishedChangingProperties(ChangeEvent); } bool FRigBaseElementDetails::IsSetupModeEnabled() const { if(BlueprintBeingCustomized) { if (UControlRig* DebuggedRig = Cast(BlueprintBeingCustomized->GetObjectBeingDebugged())) { return DebuggedRig->IsSetupModeEnabled(); } } return false; } TArray FRigBaseElementDetails::GetElementKeys() const { TArray Keys; for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { Keys.Add(ObjectBeingCustomized->GetContent().GetKey()); } } return Keys; } bool FRigBaseElementDetails::IsAnyControlOfType(ERigControlType InType) const { for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { const FRigControlElement ControlElement = ObjectBeingCustomized->GetContent(); if(ControlElement.Settings.ControlType == InType) { return true; } } } } return false; } bool FRigBaseElementDetails::IsAnyControlNotOfType(ERigControlType InType) const { for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { const FRigControlElement ControlElement = ObjectBeingCustomized->GetContent(); if(ControlElement.Settings.ControlType != InType) { return true; } } } } return false; } void FRigBaseElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule) { FRigBoneElementDetails().RegisterSectionMappings(PropertyEditorModule, UDetailsViewWrapperObject::GetClassForStruct(FRigBoneElement::StaticStruct())); FRigNullElementDetails().RegisterSectionMappings(PropertyEditorModule, UDetailsViewWrapperObject::GetClassForStruct(FRigNullElement::StaticStruct())); FRigControlElementDetails().RegisterSectionMappings(PropertyEditorModule, UDetailsViewWrapperObject::GetClassForStruct(FRigControlElement::StaticStruct())); } void FRigBaseElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass) { } void FRigTransformElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigBaseElementDetails::CustomizeDetails(DetailBuilder); } void FRigTransformElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass) { FRigBaseElementDetails::RegisterSectionMappings(PropertyEditorModule, InClass); TSharedRef TransformSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Transform", LOCTEXT("Transform", "Transform")); TransformSection->AddCategory("General"); TransformSection->AddCategory("Transform"); } void FRigTransformElementDetails::CustomizeTransform(IDetailLayoutBuilder& DetailBuilder) { IDetailCategoryBuilder& TransformCategory = DetailBuilder.EditCategory(TEXT("Transform"), LOCTEXT("Transform", "Transform")); { const TSharedPtr PoseHandle = DetailBuilder.GetProperty(TEXT("Pose")); const TSharedPtr InitialHandle = PoseHandle->GetChildHandle(TEXT("Initial")); const TSharedPtr CurrentHandle = PoseHandle->GetChildHandle(TEXT("Current")); // setup initial global { const TSharedPtr GlobalHandle = InitialHandle->GetChildHandle(TEXT("Global")); const TSharedPtr TransformHandle = GlobalHandle->GetChildHandle(TEXT("Transform")); TransformCategory.AddProperty(TransformHandle.ToSharedRef(), EPropertyLocation::Advanced) .DisplayName(FText::FromString(TEXT("Initial Global"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigBaseElementDetails::IsSetupModeEnabled))); } // setup initial local { const TSharedPtr LocalHandle = InitialHandle->GetChildHandle(TEXT("Local")); const TSharedPtr TransformHandle = LocalHandle->GetChildHandle(TEXT("Transform")); TransformCategory.AddProperty(TransformHandle.ToSharedRef(), EPropertyLocation::Advanced) .DisplayName(FText::FromString(TEXT("Initial Local"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigBaseElementDetails::IsSetupModeEnabled))); } // setup current global { const TSharedPtr GlobalHandle = CurrentHandle->GetChildHandle(TEXT("Global")); const TSharedPtr TransformHandle = GlobalHandle->GetChildHandle(TEXT("Transform")); TransformCategory.AddProperty(TransformHandle.ToSharedRef(), EPropertyLocation::Advanced) .DisplayName(FText::FromString(TEXT("Current Global"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigBaseElementDetails::IsSetupModeEnabled))); } // setup current local { const TSharedPtr LocalHandle = CurrentHandle->GetChildHandle(TEXT("Local")); const TSharedPtr TransformHandle = LocalHandle->GetChildHandle(TEXT("Transform")); TransformCategory.AddProperty(TransformHandle.ToSharedRef()) .DisplayName(FText::FromString(TEXT("Current Local"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigTransformElementDetails::IsCurrentLocalEnabled))); } } } bool FRigTransformElementDetails::IsCurrentLocalEnabled() const { for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->GetContent().GetType() == ERigElementType::Control) { return false; } } } return true; } void FRigControlElementDetails_SetupBoolValueWidget(IDetailCategoryBuilder& InCategory, const TSharedRef& InPropertyUtilities, ERigControlValueType InValueType, const FRigElementKey& InKey, URigHierarchy* InHierarchy) { UEnum* ControlTypeEnum = StaticEnum(); UEnum* ValueTypeEnum = StaticEnum(); const FString ValueTypeName = ValueTypeEnum->GetDisplayNameTextByValue((int64)InValueType).ToString(); const FText PropertyLabel = FText::FromString(FString::Printf(TEXT("%s Value"), *ValueTypeName)); TWeakObjectPtr HierarchyPtr = InHierarchy; InCategory.AddCustomRow(PropertyLabel) .NameContent() [ SNew(STextBlock) .Text(PropertyLabel) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SCheckBox) .IsChecked_Lambda([HierarchyPtr, InKey, InValueType]() -> ECheckBoxState { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { bool Value = HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); return Value ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } return ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([HierarchyPtr, InKey, InValueType](ECheckBoxState NewState) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { const FRigControlValue Value = FRigControlValue::Make(NewState == ECheckBoxState::Checked); HierarchyPtr->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); if(InValueType == ERigControlValueType::Initial) { if(UControlRigBlueprint* Blueprint = RigElementDetails_GetBlueprintFromHierarchy(HierarchyPtr.Get())) { Blueprint->Hierarchy->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); } } } } }) ] ] .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, InKey, InValueType]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.IsValueTypeEnabled(InValueType); } } return false; }))); } void FRigControlElementDetails_SetupIntegerValueWidget(IDetailCategoryBuilder& InCategory, const TSharedRef& InPropertyUtilities, ERigControlValueType InValueType, const FRigElementKey& InKey, URigHierarchy* InHierarchy) { FRigControlElement* ControlElement = InHierarchy->Find(InKey); if(ControlElement == nullptr) { return; } UEnum* ControlTypeEnum = StaticEnum(); UEnum* ValueTypeEnum = StaticEnum(); const FString ValueTypeName = ValueTypeEnum->GetDisplayNameTextByValue((int64)InValueType).ToString(); const FText PropertyLabel = FText::FromString(FString::Printf(TEXT("%s Value"), *ValueTypeName)); TWeakObjectPtr HierarchyPtr = InHierarchy; const TAttribute EnabledAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, InKey, InValueType]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.IsValueTypeEnabled(InValueType); } } return false; })); const TAttribute VisibilityAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([EnabledAttribute]()->EVisibility { return EnabledAttribute.Get() ? EVisibility::Visible : EVisibility::Hidden; })); if (ControlElement->Settings.ControlEnum) { InCategory.AddCustomRow(PropertyLabel) .Visibility(VisibilityAttribute) .NameContent() [ SNew(STextBlock) .Text(PropertyLabel) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MinDesiredWidth(125.0f * 3.0f) // copied from FComponentTransformDetails .MaxDesiredWidth(125.0f * 3.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SEnumComboBox, ControlElement->Settings.ControlEnum) .CurrentValue_Lambda([HierarchyPtr, InKey, InValueType]() -> int32 { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); } } return 0; }) .OnEnumSelectionChanged_Lambda([HierarchyPtr, InKey, InValueType](int32 NewSelection, ESelectInfo::Type) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { const FRigControlValue Value = FRigControlValue::Make(NewSelection); HierarchyPtr->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); if(InValueType == ERigControlValueType::Initial) { if(UControlRigBlueprint* Blueprint = RigElementDetails_GetBlueprintFromHierarchy(HierarchyPtr.Get())) { Blueprint->Hierarchy->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); } } } } }) .Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font"))) ] ] .IsEnabled(EnabledAttribute); } else { InCategory.AddCustomRow(PropertyLabel) .Visibility(VisibilityAttribute) .NameContent() [ SNew(STextBlock) .Text(PropertyLabel) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MinDesiredWidth(125.0f * 3.0f) // copied from FComponentTransformDetails .MaxDesiredWidth(125.0f * 3.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SNumericEntryBox) .Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font"))) .AllowSpin(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) .MinSliderValue_Lambda([HierarchyPtr, InKey, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.MinimumValue.Get(); } } } return TOptional(); }) .MaxSliderValue_Lambda([HierarchyPtr, InKey, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.MaximumValue.Get(); } } } return TOptional(); }) .Value_Lambda([HierarchyPtr, InKey, InValueType]() -> int32 { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); } } return 0; }) .OnValueChanged_Lambda([HierarchyPtr, InKey, InValueType](TOptional InNewSelection) { if(InNewSelection.IsSet()) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { const FRigControlValue Value = FRigControlValue::Make(InNewSelection.GetValue()); HierarchyPtr->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); if(InValueType == ERigControlValueType::Initial) { if(UControlRigBlueprint* Blueprint = RigElementDetails_GetBlueprintFromHierarchy(HierarchyPtr.Get())) { Blueprint->Hierarchy->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); } } } } } }) ] ] .IsEnabled(EnabledAttribute); } } void FRigControlElementDetails_SetupFloatValueWidget(IDetailCategoryBuilder& InCategory, const TSharedRef& InPropertyUtilities, ERigControlValueType InValueType, const FRigElementKey& InKey, URigHierarchy* InHierarchy) { UEnum* ControlTypeEnum = StaticEnum(); UEnum* ValueTypeEnum = StaticEnum(); const FString ValueTypeName = ValueTypeEnum->GetDisplayNameTextByValue((int64)InValueType).ToString(); const FText PropertyLabel = FText::FromString(FString::Printf(TEXT("%s Value"), *ValueTypeName)); TWeakObjectPtr HierarchyPtr = InHierarchy; const TAttribute EnabledAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, InKey, InValueType]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.IsValueTypeEnabled(InValueType); } } return false; })); const TAttribute VisibilityAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([EnabledAttribute]()->EVisibility { return EnabledAttribute.Get() ? EVisibility::Visible : EVisibility::Hidden; })); InCategory.AddCustomRow(PropertyLabel) .Visibility(VisibilityAttribute) .NameContent() [ SNew(STextBlock) .Text(PropertyLabel) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MinDesiredWidth(125.0f * 3.0f) // copied from FComponentTransformDetails .MaxDesiredWidth(125.0f * 3.0f) [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SNumericEntryBox) .Font(FEditorStyle::GetFontStyle(TEXT("MenuItem.Font"))) .AllowSpin(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) .Value_Lambda([HierarchyPtr, InKey, InValueType]() -> float { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); } } return 0.f; }) .MinSliderValue_Lambda([HierarchyPtr, InKey, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.MinimumValue.Get(); } } } return TOptional(); }) .MaxSliderValue_Lambda([HierarchyPtr, InKey, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { return ControlElement->Settings.MaximumValue.Get(); } } } return TOptional(); }) .OnValueChanged_Lambda([HierarchyPtr, InKey, InValueType](TOptional InNewSelection) { if(InNewSelection.IsSet()) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { const FRigControlValue Value = FRigControlValue::Make(InNewSelection.GetValue()); HierarchyPtr->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); if(InValueType == ERigControlValueType::Initial) { if(UControlRigBlueprint* Blueprint = RigElementDetails_GetBlueprintFromHierarchy(HierarchyPtr.Get())) { Blueprint->Hierarchy->SetControlValue(ControlElement->GetKey(), Value, InValueType, true); } } } } } }) ] ] .IsEnabled(EnabledAttribute); } template T FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { return InValue.Get(); } template FRigControlValue FRigControlElementDetails_PackageValue(const T& InValue) { return FRigControlValue::Make(InValue); } template<> FVector2D FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { const FVector3f TempValue = InValue.Get(); return FVector2D(TempValue.X, TempValue.Y); } template<> FRigControlValue FRigControlElementDetails_PackageValue(const FVector2D& InValue) { const FVector3f TempValue(InValue.X, InValue.Y, 0.f); return FRigControlValue::Make(TempValue); } template<> FVector FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { return InValue.Get(); } template<> FRigControlValue FRigControlElementDetails_PackageValue(const FVector& InValue) { return FRigControlValue::Make(InValue); } template<> FRotator FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { return FRotator::MakeFromEuler(InValue.Get()); } template<> FRigControlValue FRigControlElementDetails_PackageValue(const FRotator& InValue) { return FRigControlValue::Make(InValue.Euler()); } template<> FTransform FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { return InValue.Get().ToTransform(); } template<> FRigControlValue FRigControlElementDetails_PackageValue(const FTransform& InValue) { return FRigControlValue::Make(InValue); } template<> FTransformNoScale FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { return InValue.Get().ToTransform(); } template<> FRigControlValue FRigControlElementDetails_PackageValue(const FTransformNoScale& InValue) { return FRigControlValue::Make(InValue); } template<> FEulerTransform FRigControlElementDetails_ExtractValue(const FRigControlValue& InValue) { return InValue.Get().ToTransform(); } template<> FRigControlValue FRigControlElementDetails_PackageValue(const FEulerTransform& InValue) { return FRigControlValue::Make(InValue); } template void FRigControlElementDetails_SetupStructValueWidget(IDetailCategoryBuilder& InCategory, const TSharedRef& InPropertyUtilities, ERigControlValueType InValueType, const FRigElementKey& InKey, URigHierarchy* InHierarchy) { UEnum* ControlTypeEnum = StaticEnum(); UEnum* ValueTypeEnum = StaticEnum(); const FString ValueTypeName = ValueTypeEnum->GetDisplayNameTextByValue((int64)InValueType).ToString(); const FText PropertyLabel = FText::FromString(FString::Printf(TEXT("%s Value"), *ValueTypeName)); const UScriptStruct* ValueStruct = TBaseStructure::Get(); const TSharedPtr StructToDisplay = MakeShareable(new FStructOnScope(ValueStruct)); TWeakObjectPtr HierarchyPtr = InHierarchy; const TAttribute VisibilityAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, InKey, InValueType, StructToDisplay, ValueStruct]()->EVisibility { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(InKey)) { // update the struct with the current control value uint8* StructMemory = StructToDisplay->GetStructMemory(); const FRigControlValue& ControlValue = HierarchyPtr->GetControlValue(ControlElement, InValueType); T ExtractedValue = FRigControlElementDetails_ExtractValue(ControlValue); ValueStruct->CopyScriptStruct(StructToDisplay->GetStructMemory(), &ExtractedValue, 1); if(ControlElement->Settings.IsValueTypeEnabled(InValueType)) { return EVisibility::Visible; } } } return EVisibility::Hidden; })); IDetailPropertyRow* Row = InCategory.AddExternalStructure(StructToDisplay.ToSharedRef()); Row->DisplayName(PropertyLabel); Row->ShouldAutoExpand(true); Row->Visibility(VisibilityAttribute); TSharedPtr NameWidget, ValueWidget; Row->GetDefaultWidgets(NameWidget, ValueWidget); const FSimpleDelegate OnStructContentsChangedDelegate = FSimpleDelegate::CreateLambda([HierarchyPtr, InKey, StructToDisplay, InValueType]() { if(HierarchyPtr.IsValid()) { const FRigControlValue ControlValue = FRigControlElementDetails_PackageValue(*(T*)StructToDisplay->GetStructMemory()); HierarchyPtr->SetControlValue(InKey, ControlValue, InValueType, true, true); if(InValueType == ERigControlValueType::Initial) { if(UControlRigBlueprint* Blueprint = RigElementDetails_GetBlueprintFromHierarchy(HierarchyPtr.Get())) { Blueprint->Hierarchy->SetControlValue(InKey, ControlValue, InValueType, true); } } } }); TSharedPtr Handle = Row->GetPropertyHandle(); Handle->SetOnPropertyValueChanged(OnStructContentsChangedDelegate); Handle->SetOnChildPropertyValueChanged(OnStructContentsChangedDelegate); } void FRigControlElementDetails_SetupValueWidget(IDetailCategoryBuilder& InCategory, const TSharedRef& InPropertyUtilities, ERigControlValueType InValueType, const FRigElementKey& InKey, URigHierarchy* InHierarchy) { const FRigControlElement* ControlElement = InHierarchy->Find(InKey); if(ControlElement == nullptr) { return; } switch(ControlElement->Settings.ControlType) { case ERigControlType::Bool: { if((InValueType == ERigControlValueType::Minimum) || (InValueType == ERigControlValueType::Maximum)) { return; } FRigControlElementDetails_SetupBoolValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::Integer: { FRigControlElementDetails_SetupIntegerValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::Float: { FRigControlElementDetails_SetupFloatValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::Vector2D: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::Position: case ERigControlType::Scale: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::Rotator: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::TransformNoScale: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::EulerTransform: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } case ERigControlType::Transform: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InPropertyUtilities, InValueType, InKey, InHierarchy); break; } default: { ensure(false); break; } } } void FRigBoneElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigTransformElementDetails::CustomizeDetails(DetailBuilder); CustomizeTransform(DetailBuilder); } TArray> FRigControlElementDetails::ControlTypeList; void FRigControlElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigTransformElementDetails::CustomizeDetails(DetailBuilder); ShapeNameList.Reset(); if (BlueprintBeingCustomized) { bool bUseNameSpace = BlueprintBeingCustomized->ShapeLibraries.Num() > 1; for(TSoftObjectPtr& ShapeLibrary : BlueprintBeingCustomized->ShapeLibraries) { if (!ShapeLibrary.IsValid()) { ShapeLibrary.LoadSynchronous(); } if (ShapeLibrary.IsValid()) { const FString NameSpace = bUseNameSpace ? ShapeLibrary->GetName() + TEXT(".") : FString(); ShapeNameList.Add(MakeShared(NameSpace + ShapeLibrary->DefaultShape.ShapeName.ToString())); for (const FControlRigShapeDefinition& Shape : ShapeLibrary->Shapes) { ShapeNameList.Add(MakeShared(NameSpace + Shape.ShapeName.ToString())); } } } } if (HierarchyBeingCustomized == nullptr) { return; } IDetailCategoryBuilder& ControlCategory = DetailBuilder.EditCategory(TEXT("Control"), LOCTEXT("Control", "Control")); IDetailCategoryBuilder& ShapeCategory = DetailBuilder.EditCategory(TEXT("Shape"), LOCTEXT("Shape", "Shape")); IDetailCategoryBuilder& LimitsCategory = DetailBuilder.EditCategory(TEXT("Limits"), LOCTEXT("Limits", "Limits")); // the transform category should show up after the control, shape and limits categories CustomizeTransform(DetailBuilder); const TSharedPtr SettingsHandle = DetailBuilder.GetProperty(TEXT("Settings")); const TSharedPtr DisplayNameHandle = SettingsHandle->GetChildHandle(TEXT("DisplayName")); DetailBuilder.HideProperty(SettingsHandle); ControlCategory.AddCustomRow(LOCTEXT("DisplayName", "Display Name")) .NameContent() [ DisplayNameHandle->CreatePropertyNameWidget() ] .ValueContent() [ SNew(SEditableTextBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FRigControlElementDetails::GetDisplayName) .OnTextCommitted(this, &FRigControlElementDetails::SetDisplayName) .IsEnabled(ObjectsBeingCustomized.Num() == 1) ]; if (ControlTypeList.Num() == 0) { UEnum* Enum = StaticEnum(); for (int64 Index = 0; Index < Enum->GetMaxEnumValue(); Index++) { ControlTypeList.Add(MakeShared(Enum->GetDisplayNameTextByValue(Index).ToString())); } } const TSharedRef PropertyUtilities = DetailBuilder.GetPropertyUtilities(); // when control type changes, we have to refresh detail panel const TSharedPtr ControlTypeHandle = SettingsHandle->GetChildHandle(TEXT("ControlType")); ControlTypeHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda( [this, PropertyUtilities]() { TArray ControlElementsInView = GetElementsInDetailsView(); TArray ControlElementsInHierarchy = GetElementsInHierarchy(); check(ControlElementsInView.Num() == ControlElementsInHierarchy.Num()); if (this->HierarchyBeingCustomized && ControlElementsInHierarchy.Num() > 0) { for(int32 ControlIndex = 0; ControlIndex< ControlElementsInView.Num(); ControlIndex++) { const FRigControlElement& ViewElement = ControlElementsInView[ControlIndex]; FRigControlElement* ControlElement = ControlElementsInHierarchy[ControlIndex]; FRigControlValue ValueToSet; ControlElement->Settings.ControlType = ViewElement.Settings.ControlType; ControlElement->Settings.bLimitTranslation = false; ControlElement->Settings.bLimitRotation = false; ControlElement->Settings.bLimitScale = false; switch (ControlElement->Settings.ControlType) { case ERigControlType::Bool: { ValueToSet = FRigControlValue::Make(false); break; } case ERigControlType::Float: { ValueToSet = FRigControlValue::Make(0.f); ControlElement->Settings.bLimitTranslation = true; ControlElement->Settings.MinimumValue = FRigControlValue::Make(0.f); ControlElement->Settings.MaximumValue = FRigControlValue::Make(100.f); break; } case ERigControlType::Integer: { ValueToSet = FRigControlValue::Make(0); ControlElement->Settings.bLimitTranslation = true; ControlElement->Settings.MinimumValue = FRigControlValue::Make(0); ControlElement->Settings.MaximumValue = FRigControlValue::Make(100); break; } case ERigControlType::Vector2D: { ValueToSet = FRigControlValue::Make(FVector2D::ZeroVector); ControlElement->Settings.bLimitTranslation = true; ControlElement->Settings.MinimumValue = FRigControlValue::Make(FVector2D::ZeroVector); ControlElement->Settings.MaximumValue = FRigControlValue::Make(FVector2D(100.f, 100.f)); break; } case ERigControlType::Position: { ValueToSet = FRigControlValue::Make(FVector::ZeroVector); ControlElement->Settings.MinimumValue = FRigControlValue::Make(-FVector::OneVector); ControlElement->Settings.MaximumValue = FRigControlValue::Make(FVector::OneVector); break; } case ERigControlType::Scale: { ValueToSet = FRigControlValue::Make(FVector::OneVector); ControlElement->Settings.MinimumValue = FRigControlValue::Make(FVector::ZeroVector); ControlElement->Settings.MaximumValue = FRigControlValue::Make(FVector::OneVector); break; } case ERigControlType::Rotator: { ValueToSet = FRigControlValue::Make(FRotator::ZeroRotator); ControlElement->Settings.MinimumValue = FRigControlValue::Make(FRotator::ZeroRotator); ControlElement->Settings.MaximumValue = FRigControlValue::Make(FRotator(180.f, 180.f, 180.f)); break; } case ERigControlType::Transform: { ValueToSet = FRigControlValue::Make(FTransform::Identity); ControlElement->Settings.MinimumValue = ValueToSet; ControlElement->Settings.MaximumValue = ValueToSet; break; } case ERigControlType::TransformNoScale: { FTransformNoScale Identity = FTransform::Identity; ValueToSet = FRigControlValue::Make(Identity); ControlElement->Settings.MinimumValue = ValueToSet; ControlElement->Settings.MaximumValue = ValueToSet; break; } case ERigControlType::EulerTransform: { FEulerTransform Identity = FEulerTransform::Identity; ValueToSet = FRigControlValue::Make(Identity); ControlElement->Settings.MinimumValue = ValueToSet; ControlElement->Settings.MaximumValue = ValueToSet; break; } default: { ensure(false); break; } } this->HierarchyBeingCustomized->Notify(ERigHierarchyNotification::ControlSettingChanged, ControlElement); this->HierarchyBeingCustomized->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Initial, true, false, true); this->HierarchyBeingCustomized->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Current, true, false, true); ObjectsBeingCustomized[ControlIndex]->SetContent(*ControlElement); if (this->HierarchyBeingCustomized != this->BlueprintBeingCustomized->Hierarchy) { if(FRigControlElement* OtherControlElement = this->BlueprintBeingCustomized->Hierarchy->Find(ControlElement->GetKey())) { OtherControlElement->Settings = ControlElement->Settings; this->BlueprintBeingCustomized->Hierarchy->Notify(ERigHierarchyNotification::ControlSettingChanged, ControlElement); this->BlueprintBeingCustomized->Hierarchy->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Initial, true); this->BlueprintBeingCustomized->Hierarchy->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Current, true); } } } PropertyUtilities->ForceRefresh(); } } )); ControlCategory.AddProperty(ControlTypeHandle.ToSharedRef()); ControlCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("bAnimatable")).ToSharedRef()); // any but bool controls show the offset + shape + limits const bool bNeedsShapeProperties = IsAnyControlNotOfType(ERigControlType::Bool); if (bNeedsShapeProperties) { // setup offset { const TSharedPtr OffsetHandle = DetailBuilder.GetProperty(TEXT("Offset")); const TSharedPtr InitialHandle = OffsetHandle->GetChildHandle(TEXT("Initial")); const TSharedPtr LocalHandle = InitialHandle->GetChildHandle(TEXT("Local")); const TSharedPtr TransformHandle = LocalHandle->GetChildHandle(TEXT("Transform")); ControlCategory.AddProperty(TransformHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Offset Transform"))); } } if(IsAnyControlOfType(ERigControlType::Float) || IsAnyControlOfType(ERigControlType::Integer) || IsAnyControlOfType(ERigControlType::Vector2D) || IsAnyControlOfType(ERigControlType::Position) || IsAnyControlOfType(ERigControlType::Transform) || IsAnyControlOfType(ERigControlType::TransformNoScale) || IsAnyControlOfType(ERigControlType::EulerTransform)) { const TSharedPtr LimitHandle = SettingsHandle->GetChildHandle(TEXT("bLimitTranslation")); LimitsCategory.AddProperty(LimitHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Limit Translation"))); } if(IsAnyControlOfType(ERigControlType::Rotator) || IsAnyControlOfType(ERigControlType::Transform) || IsAnyControlOfType(ERigControlType::TransformNoScale) || IsAnyControlOfType(ERigControlType::EulerTransform)) { const TSharedPtr LimitHandle = SettingsHandle->GetChildHandle(TEXT("bLimitRotation")); LimitsCategory.AddProperty(LimitHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Limit Rotation"))); } if(IsAnyControlOfType(ERigControlType::Scale) || IsAnyControlOfType(ERigControlType::Transform) || IsAnyControlOfType(ERigControlType::EulerTransform)) { const TSharedPtr LimitHandle = SettingsHandle->GetChildHandle(TEXT("bLimitScale")); LimitsCategory.AddProperty(LimitHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Limit Scale"))); } if(!(IsAnyControlNotOfType(ERigControlType::Integer) && IsAnyControlNotOfType(ERigControlType::Float) && IsAnyControlNotOfType(ERigControlType::Vector2D))) { const TSharedPtr PrimaryAxisHandle = SettingsHandle->GetChildHandle(TEXT("PrimaryAxis")); ControlCategory.AddProperty(PrimaryAxisHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Primary Axis"))); } if (bNeedsShapeProperties) { const TSharedPtr DrawLimitsHandle = SettingsHandle->GetChildHandle(TEXT("bDrawLimits")); LimitsCategory.AddProperty(DrawLimitsHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Draw Limits"))); } TArray ControlElements; TArray ObjectPerControl; for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { ControlElements.Add(ObjectBeingCustomized->GetContent()); ObjectPerControl.Add(ObjectBeingCustomized.Get()); } } } // only setup value widgets if there is only ony control selected if(ControlElements.Num() == 1) { URigHierarchy* HierarchyBeingDebugged = HierarchyBeingCustomized; if (UControlRig* DebuggedRig = Cast(BlueprintBeingCustomized->GetObjectBeingDebugged())) { if(!DebuggedRig->IsSetupModeEnabled()) { HierarchyBeingDebugged = DebuggedRig->GetHierarchy(); } } FRigControlElementDetails_SetupValueWidget(ControlCategory, PropertyUtilities, ERigControlValueType::Current, ControlElements[0].GetKey(), HierarchyBeingDebugged); switch (ControlElements[0].Settings.ControlType) { case ERigControlType::Bool: case ERigControlType::Float: case ERigControlType::Integer: case ERigControlType::Vector2D: { FRigControlElementDetails_SetupValueWidget(ControlCategory, PropertyUtilities, ERigControlValueType::Initial,ControlElements[0].GetKey(), HierarchyBeingCustomized); break; } default: { break; } } FRigControlElementDetails_SetupValueWidget(LimitsCategory, PropertyUtilities, ERigControlValueType::Minimum, ControlElements[0].GetKey(), HierarchyBeingCustomized); FRigControlElementDetails_SetupValueWidget(LimitsCategory, PropertyUtilities, ERigControlValueType::Maximum, ControlElements[0].GetKey(), HierarchyBeingCustomized); } if (bNeedsShapeProperties) { ShapeCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("bShapeEnabled")).ToSharedRef()) .DisplayName(FText::FromString(TEXT("Enabled"))); ShapeCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("bShapeVisible")).ToSharedRef()) .DisplayName(FText::FromString(TEXT("Visible"))); IDetailGroup& ShapeProperitesGroup = ShapeCategory.AddGroup(TEXT("Shape Properties"), LOCTEXT("ShapeProperties", "Shape Properties")); ShapeProperitesGroup.HeaderRow().NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("ShapeProperties", "Shape Properties")) .ToolTipText(LOCTEXT("ShapeProperties", "Customize the properties of the shape")) ] .CopyAction(FUIAction( FExecuteAction::CreateSP(this, &FRigControlElementDetails::OnCopyShapeProperties))) .PasteAction(FUIAction( FExecuteAction::CreateSP(this, &FRigControlElementDetails::OnPasteShapeProperties))); { // setup shape transform { TSharedPtr ShapeHandle = DetailBuilder.GetProperty(TEXT("Shape")); TSharedPtr InitialHandle = ShapeHandle->GetChildHandle(TEXT("Initial")); TSharedPtr LocalHandle = InitialHandle->GetChildHandle(TEXT("Local")); ShapeTransformHandle = LocalHandle->GetChildHandle(TEXT("Transform")); ShapeProperitesGroup.AddPropertyRow(ShapeTransformHandle.ToSharedRef()) .IsEnabled(TAttribute::CreateSP(this, &FRigControlElementDetails::IsShapeEnabled)); } ShapeNameHandle = SettingsHandle->GetChildHandle(TEXT("ShapeName")); ShapeProperitesGroup.AddPropertyRow(ShapeNameHandle.ToSharedRef()).CustomWidget() .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Shape"))) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(this, &FRigControlElementDetails::IsShapeEnabled) ] .ValueContent() [ SNew(SControlRigShapeNameList, ControlElements, BlueprintBeingCustomized) .OnGetNameListContent(this, &FRigControlElementDetails::GetShapeNameList) .IsEnabled(this, &FRigControlElementDetails::IsShapeEnabled) ]; ShapeColorHandle = SettingsHandle->GetChildHandle(TEXT("ShapeColor")); ShapeProperitesGroup.AddPropertyRow(ShapeColorHandle.ToSharedRef()) .DisplayName(FText::FromString(TEXT("Color"))); } } if(IsAnyControlOfType(ERigControlType::Integer)) { const TSharedPtr ControlEnumHandle = SettingsHandle->GetChildHandle(TEXT("ControlEnum")); ControlCategory.AddProperty(ControlEnumHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Control Enum"))); ControlEnumHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda( [this, PropertyUtilities, ControlElements, ObjectPerControl]() { PropertyUtilities->ForceRefresh(); if (this->HierarchyBeingCustomized != nullptr) { for(int32 ControlIndex = 0; ControlIndex < ControlElements.Num(); ControlIndex++) { const FRigControlElement ControlInView = ControlElements[ControlIndex]; FRigControlElement* ControlBeingCustomized = HierarchyBeingCustomized->Find(ControlInView.GetKey()); const UEnum* ControlEnum = ControlBeingCustomized->Settings.ControlEnum; if (ControlEnum != nullptr) { int32 Maximum = (int32)ControlEnum->GetMaxEnumValue() - 1; ControlBeingCustomized->Settings.MinimumValue.Set(0); ControlBeingCustomized->Settings.MaximumValue.Set(Maximum); HierarchyBeingCustomized->Notify(ERigHierarchyNotification::ControlSettingChanged, ControlBeingCustomized); FRigControlValue InitialValue = HierarchyBeingCustomized->GetControlValue(ControlBeingCustomized, ERigControlValueType::Initial); FRigControlValue CurrentValue = HierarchyBeingCustomized->GetControlValue(ControlBeingCustomized, ERigControlValueType::Current); ControlBeingCustomized->Settings.ApplyLimits(InitialValue); ControlBeingCustomized->Settings.ApplyLimits(CurrentValue); HierarchyBeingCustomized->SetControlValue(ControlBeingCustomized, InitialValue, ERigControlValueType::Initial, false, false, true); HierarchyBeingCustomized->SetControlValue(ControlBeingCustomized, CurrentValue, ERigControlValueType::Current, false, false, true); if (UControlRig* DebuggedRig = Cast(BlueprintBeingCustomized->GetObjectBeingDebugged())) { URigHierarchy* DebuggedHierarchy = DebuggedRig->GetHierarchy(); if(FRigControlElement* DebuggedControlElement = DebuggedHierarchy->Find(ControlBeingCustomized->GetKey())) { DebuggedControlElement->Settings.MinimumValue.Set(0); DebuggedControlElement->Settings.MaximumValue.Set(Maximum); DebuggedHierarchy->Notify(ERigHierarchyNotification::ControlSettingChanged, DebuggedControlElement); DebuggedHierarchy->SetControlValue(DebuggedControlElement, InitialValue, ERigControlValueType::Initial); DebuggedHierarchy->SetControlValue(DebuggedControlElement, CurrentValue, ERigControlValueType::Current); } } } ObjectPerControl[ControlIndex]->SetContent(*ControlBeingCustomized); } } } )); } IDetailCategoryBuilder& AnimationCategory = DetailBuilder.EditCategory(TEXT("Animation"), LOCTEXT("Animation", "Animation")); AnimationCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("Customization"))->GetChildHandle(TEXT("AvailableSpaces")).ToSharedRef()); } void FRigControlElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass) { FRigTransformElementDetails::RegisterSectionMappings(PropertyEditorModule, InClass); TSharedRef ControlSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Control", LOCTEXT("Control", "Control")); ControlSection->AddCategory("General"); ControlSection->AddCategory("Control"); ControlSection->AddCategory("Animation"); TSharedRef ShapeSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Shape", LOCTEXT("Shape", "Shape")); ShapeSection->AddCategory("General"); ShapeSection->AddCategory("Shape"); TSharedRef LimitsSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Limits", LOCTEXT("Limits", "Limits")); LimitsSection->AddCategory("General"); LimitsSection->AddCategory("Limits"); } bool FRigControlElementDetails::IsShapeEnabled() const { for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { const FRigControlElement ControlElement = ObjectBeingCustomized->GetContent(); if(ControlElement.Settings.bShapeEnabled) { return true; } } } } return false; } bool FRigControlElementDetails::IsEnabled(ERigControlValueType InValueType) const { for(TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { const FRigControlElement ControlElement = ObjectBeingCustomized->GetContent(); switch (InValueType) { case ERigControlValueType::Minimum: case ERigControlValueType::Maximum: { if(ControlElement.Settings.bLimitTranslation || ControlElement.Settings.bLimitRotation || ControlElement.Settings.bLimitScale) { return true; } } default: { break; } } } } } return true; } const TArray>& FRigControlElementDetails::GetShapeNameList() const { return ShapeNameList; } const TArray>& FRigControlElementDetails::GetControlTypeList() const { return ControlTypeList; } FText FRigControlElementDetails::GetDisplayName() const { FName DisplayName(NAME_None); for(int32 ObjectIndex = 0; ObjectIndex < ObjectsBeingCustomized.Num(); ObjectIndex++) { TWeakObjectPtr ObjectBeingCustomized = ObjectsBeingCustomized[ObjectIndex]; if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { const FRigControlElement ControlElement = ObjectBeingCustomized->GetContent(); if(ObjectIndex == 0) { DisplayName = ControlElement.Settings.DisplayName; } else if(DisplayName != ControlElement.Settings.DisplayName) { return ControlRigDetailsMultipleValues; } } } } if(!DisplayName.IsNone()) { return FText::FromName(DisplayName); } return FText(); } void FRigControlElementDetails::SetDisplayName(const FText& InNewText, ETextCommit::Type InCommitType) { if(InCommitType == ETextCommit::OnCleared) { return; } const FName DisplayName = InNewText.IsEmpty() ? FName(NAME_None) : FName(*InNewText.ToString()); for(int32 ObjectIndex = 0; ObjectIndex < ObjectsBeingCustomized.Num(); ObjectIndex++) { TWeakObjectPtr ObjectBeingCustomized = ObjectsBeingCustomized[ObjectIndex]; if(ObjectBeingCustomized.IsValid()) { if(ObjectBeingCustomized->IsChildOf()) { FRigControlElement ControlElement = ObjectBeingCustomized->GetContent(); ControlElement.Settings.DisplayName = DisplayName; ObjectBeingCustomized->SetContent(ControlElement); if(GetHierarchy()) { GetHierarchy()->SetControlSettings(ControlElement.GetKey(), ControlElement.Settings, false, false, true); } } } } } void FRigControlElementDetails::OnCopyShapeProperties() { FString Value; if (ObjectsBeingCustomized.Num() > 0) { if(ObjectsBeingCustomized[0].IsValid()) { if(ObjectsBeingCustomized[0]->IsChildOf()) { const FRigControlElement ControlElement = ObjectsBeingCustomized[0]->GetContent(); Value = FString::Printf(TEXT("(ShapeName=\"%s\",ShapeColor=%s,Transform=%s)"), *ControlElement.Settings.ShapeName.ToString(), *ControlElement.Settings.ShapeColor.ToString(), *ControlElement.Shape.Initial.Local.Transform.ToString()); } } } if (!Value.IsEmpty()) { // Copy. FPlatformApplicationMisc::ClipboardCopy(*Value); } } void FRigControlElementDetails::OnPasteShapeProperties() { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); FString TrimmedText = PastedText.LeftChop(1).RightChop(1); FString ShapeName; FString ShapeColorStr; FString TransformStr; bool bSuccessful = FParse::Value(*TrimmedText, TEXT("ShapeName="), ShapeName) && FParse::Value(*TrimmedText, TEXT("ShapeColor="), ShapeColorStr, false) && FParse::Value(*TrimmedText, TEXT("Transform="), TransformStr, false); if (bSuccessful) { FScopedTransaction Transaction(LOCTEXT("PasteShape", "Paste Shape")); // Name { ShapeNameHandle->NotifyPreChange(); ShapeNameHandle->SetValue(ShapeName); ShapeNameHandle->NotifyPostChange(EPropertyChangeType::ValueSet); } // Color { ShapeColorHandle->NotifyPreChange(); TArray RawDataPtrs; ShapeColorHandle->AccessRawData(RawDataPtrs); for (void* RawPtr: RawDataPtrs) { bSuccessful &= static_cast(RawPtr)->InitFromString(ShapeColorStr); if (!bSuccessful) { Transaction.Cancel(); return; } } ShapeColorHandle->NotifyPostChange(EPropertyChangeType::ValueSet); } // Transform { ShapeTransformHandle->NotifyPreChange(); TArray RawDataPtrs; ShapeTransformHandle->AccessRawData(RawDataPtrs); for (void* RawPtr: RawDataPtrs) { bSuccessful &= static_cast(RawPtr)->InitFromString(TransformStr); if (!bSuccessful) { Transaction.Cancel(); return; } } ShapeTransformHandle->NotifyPostChange(EPropertyChangeType::ValueSet); } } } void FRigNullElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigTransformElementDetails::CustomizeDetails(DetailBuilder); CustomizeTransform(DetailBuilder); } #undef LOCTEXT_NAMESPACE