// 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 "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" #define LOCTEXT_NAMESPACE "ControlRigElementDetails" 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)) { OutBlueprint = Cast(Rig->GetClass()->ClassGeneratedBy); if (OutBlueprint) { 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) .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()) { FName ElementName; NameHandle->GetValue(ElementName); ElementNameStr = ElementName.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("Gizmo."))) { PropertyPath.RightChopInline(6); PropertyChain.AddTail(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Gizmo"))); } 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) { DetailBuilder.HideProperty(TEXT("Key"), FRigBaseElement::StaticStruct()); DetailBuilder.HideProperty(TEXT("Index"), FRigBaseElement::StaticStruct()); DetailBuilder.HideProperty(TEXT("SubIndex"), FRigBaseElement::StaticStruct()); TArray> StructsBeingCustomized; DetailBuilder.GetStructsBeingCustomized(StructsBeingCustomized); for (TSharedPtr StructBeingCustomized : StructsBeingCustomized) { 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) { HierarchyBeingCustomized = BlueprintBeingCustomized->Hierarchy; if (UControlRig* DebuggedControlRig = Cast(BlueprintBeingCustomized->GetObjectBeingDebugged())) { if (!DebuggedControlRig->IsSetupModeEnabled()) { HierarchyBeingCustomized = DebuggedControlRig->GetHierarchy(); } } break; } } } if (HierarchyBeingCustomized) { ElementKeyBeingCustomized = ((const FRigBaseElement*)StructBeingCustomized->GetStructMemory())->GetKey(); break; } } } IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("RigElement")); Category.InitiallyCollapsed(false); Category.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) ]; } void FRigBaseElementDetails::SetName(const FText& InNewText, ETextCommit::Type InCommitType) { URigHierarchy* Hierarchy = nullptr; if (BlueprintBeingCustomized) { Hierarchy = BlueprintBeingCustomized->Hierarchy; } else { Hierarchy = GetHierarchy(); } if (Hierarchy) { URigHierarchyController* Controller = Hierarchy->GetController(true); check(Controller); Controller->RenameElement(ElementKeyBeingCustomized, *InNewText.ToString(), 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; } void FRigTransformElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigBaseElementDetails::CustomizeDetails(DetailBuilder); //if(ElementKeyBeingCustomized.Type != ERigElementType::Control) { IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("Pose")); if(ElementKeyBeingCustomized.Type == ERigElementType::Control) { Category .InitiallyCollapsed(true); } // setup initial global { const TSharedRef PropertyHandle = DetailBuilder.GetProperty(TEXT("Pose.Initial.Global.Transform"), FRigTransformElement::StaticStruct()); Category.AddProperty(PropertyHandle, EPropertyLocation::Advanced).DisplayName(FText::FromString(TEXT("Initial Global"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigBaseElementDetails::IsSetupModeEnabled))); PoseInitialGlobal.AddHead(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Pose"))); PoseInitialGlobal.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Initial"))); PoseInitialGlobal.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Global"))); PoseInitialGlobal.AddTail(FRigComputedTransform::StaticStruct()->FindPropertyByName(TEXT("Transform"))); PoseInitialGlobal.SetActiveMemberPropertyNode(PoseInitialGlobal.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateStatic(&FRigTransformElementDetails::OnTransformChanged, &PoseInitialGlobal, BlueprintBeingCustomized); PropertyHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetToolTipText(FText::FromString(TEXT("The initial / ref pose global transform in the space of the rig / actor."))); } // setup initial local { const TSharedRef PropertyHandle = DetailBuilder.GetProperty(TEXT("Pose.Initial.Local.Transform"), FRigTransformElement::StaticStruct()); Category.AddProperty(PropertyHandle, EPropertyLocation::Advanced).DisplayName(FText::FromString(TEXT("Initial Local"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigBaseElementDetails::IsSetupModeEnabled))); PoseInitialLocal.AddHead(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Pose"))); PoseInitialLocal.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Initial"))); PoseInitialLocal.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Local"))); PoseInitialLocal.AddTail(FRigComputedTransform::StaticStruct()->FindPropertyByName(TEXT("Transform"))); PoseInitialLocal.SetActiveMemberPropertyNode(PoseInitialLocal.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateStatic(&FRigTransformElementDetails::OnTransformChanged, &PoseInitialLocal, BlueprintBeingCustomized); PropertyHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetToolTipText(FText::FromString(TEXT("The initial / ref pose local transform in the space of the parent.\nFor Controls the initial value is relative to the offset."))); } // setup current global { const TSharedRef PropertyHandle = DetailBuilder.GetProperty(TEXT("Pose.Current.Global.Transform"), FRigTransformElement::StaticStruct()); Category.AddProperty(PropertyHandle, EPropertyLocation::Advanced).DisplayName(FText::FromString(TEXT("Current Global"))) .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FRigBaseElementDetails::IsSetupModeEnabled))); PoseCurrentGlobal.AddHead(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Pose"))); PoseCurrentGlobal.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Current"))); PoseCurrentGlobal.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Global"))); PoseCurrentGlobal.AddTail(FRigComputedTransform::StaticStruct()->FindPropertyByName(TEXT("Transform"))); PoseCurrentGlobal.SetActiveMemberPropertyNode(PoseCurrentGlobal.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateStatic(&FRigTransformElementDetails::OnTransformChanged, &PoseCurrentGlobal, BlueprintBeingCustomized); PropertyHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetToolTipText(FText::FromString(TEXT("The current global transform in the space of the rig / actor."))); } // setup current local { const TSharedRef PropertyHandle = DetailBuilder.GetProperty(TEXT("Pose.Current.Local.Transform"), FRigTransformElement::StaticStruct()); Category.AddProperty(PropertyHandle).DisplayName(FText::FromString(TEXT("Current Local"))) .IsEnabled(ElementKeyBeingCustomized.Type != ERigElementType::Control); PoseCurrentLocal.AddHead(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Pose"))); PoseCurrentLocal.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Current"))); PoseCurrentLocal.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Local"))); PoseCurrentLocal.AddTail(FRigComputedTransform::StaticStruct()->FindPropertyByName(TEXT("Transform"))); PoseCurrentLocal.SetActiveMemberPropertyNode(PoseCurrentLocal.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateStatic(&FRigTransformElementDetails::OnTransformChanged, &PoseCurrentLocal, BlueprintBeingCustomized); PropertyHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); PropertyHandle->SetToolTipText(FText::FromString(TEXT("The current local transform in the space of the parent.\nFor Controls the initial value is relative to the offset."))); } DetailBuilder.HideProperty(TEXT("Pose"), FRigTransformElement::StaticStruct()); } } void FRigTransformElementDetails::OnTransformChanged(FEditPropertyChain* InPropertyChain, UControlRigBlueprint* InBlueprint) { if(InBlueprint && InPropertyChain) { if(InPropertyChain->Num() > 1) { FPropertyChangedEvent ChangeEvent(InPropertyChain->GetHead()->GetValue(), EPropertyChangeType::ValueSet); ChangeEvent.SetActiveMemberProperty(InPropertyChain->GetTail()->GetValue()); FPropertyChangedChainEvent ChainEvent(*InPropertyChain, ChangeEvent); InBlueprint->BroadcastPostEditChangeChainProperty(ChainEvent); } } } void FRigBoneElementDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) { FRigTransformElementDetails::CustomizeDetails(DetailBuilder); } void FRigControlElementDetails_SetupBoolValueWidget(IDetailCategoryBuilder& InCategory, ERigControlValueType InValueType, FRigControlElement* InControlElement, 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 FRigElementKey Key = InControlElement->GetKey(); InCategory.AddCustomRow(PropertyLabel) .NameContent() [ SNew(STextBlock) .Text(PropertyLabel) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SVerticalBox) + SVerticalBox::Slot() [ SNew(SCheckBox) .IsChecked_Lambda([HierarchyPtr, Key, InValueType]() -> ECheckBoxState { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { bool Value = HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); return Value ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } return ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([HierarchyPtr, Key, InValueType](ECheckBoxState NewState) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { 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, Key, InValueType]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return ControlElement->Settings.IsValueTypeEnabled(InValueType); } } return false; }))); } void FRigControlElementDetails_SetupIntegerValueWidget(IDetailCategoryBuilder& InCategory, ERigControlValueType InValueType, FRigControlElement* InControlElement, 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 FRigElementKey Key = InControlElement->GetKey(); const TAttribute EnabledAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, Key, InValueType]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { 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 (InControlElement->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, InControlElement->Settings.ControlEnum) .CurrentValue_Lambda([HierarchyPtr, Key, InValueType]() -> int32 { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); } } return 0; }) .OnEnumSelectionChanged_Lambda([HierarchyPtr, Key, InValueType](int32 NewSelection, ESelectInfo::Type) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { 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, Key, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return ControlElement->Settings.MinimumValue.Get(); } } } return TOptional(); }) .MaxSliderValue_Lambda([HierarchyPtr, Key, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return ControlElement->Settings.MaximumValue.Get(); } } } return TOptional(); }) .Value_Lambda([HierarchyPtr, Key, InValueType]() -> int32 { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); } } return 0; }) .OnValueChanged_Lambda([HierarchyPtr, Key, InValueType](TOptional InNewSelection) { if(InNewSelection.IsSet()) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { 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, ERigControlValueType InValueType, FRigControlElement* InControlElement, 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 FRigElementKey Key = InControlElement->GetKey(); const TAttribute EnabledAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, Key, InValueType]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { 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, Key, InValueType]() -> float { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return HierarchyPtr->GetControlValue(ControlElement, InValueType).Get(); } } return 0.f; }) .MinSliderValue_Lambda([HierarchyPtr, Key, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return ControlElement->Settings.MinimumValue.Get(); } } } return TOptional(); }) .MaxSliderValue_Lambda([HierarchyPtr, Key, InValueType]() -> TOptional { if(InValueType == ERigControlValueType::Current || InValueType == ERigControlValueType::Initial) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { return ControlElement->Settings.MaximumValue.Get(); } } } return TOptional(); }) .OnValueChanged_Lambda([HierarchyPtr, Key, InValueType](TOptional InNewSelection) { if(InNewSelection.IsSet()) { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { 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 void FRigControlElementDetails_SetupStructValueWidget(IDetailCategoryBuilder& InCategory, ERigControlValueType InValueType, FRigControlElement* InControlElement, 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 UStruct* ValueStruct = TBaseStructure::Get(); const TSharedPtr StructToDisplay = MakeShareable(new FStructOnScope(ValueStruct)); TWeakObjectPtr HierarchyPtr = InHierarchy; const FRigElementKey Key = InControlElement->GetKey(); const TAttribute EnabledAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, Key, InValueType, StructToDisplay, ValueStruct]()->bool { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { // update the struct with the current control value uint8* StructMemory = StructToDisplay->GetStructMemory(); const FRigControlValue& CurrentValue = HierarchyPtr->GetControlValue(Key, InValueType); FMemory::Memcpy(StructToDisplay->GetStructMemory(), &CurrentValue.GetRef(), sizeof(T)); return ControlElement->Settings.IsValueTypeEnabled(InValueType); } } return false; })); const TAttribute VisibilityAttribute = TAttribute::Create(TAttribute::FGetter::CreateLambda([HierarchyPtr, Key, InValueType]()->EVisibility { if(HierarchyPtr.IsValid()) { if(FRigControlElement* ControlElement = HierarchyPtr->Find(Key)) { if(ControlElement->Settings.IsValueTypeEnabled(InValueType)) { return EVisibility::Visible; } } } return EVisibility::Hidden; })); IDetailPropertyRow* Row = InCategory.AddExternalStructure(StructToDisplay); Row->DisplayName(PropertyLabel); Row->ShouldAutoExpand(true); Row->IsEnabled(EnabledAttribute); Row->Visibility(VisibilityAttribute); TSharedPtr NameWidget, ValueWidget; Row->GetDefaultWidgets(NameWidget, ValueWidget); const FSimpleDelegate OnStructContentsChangedDelegate = FSimpleDelegate::CreateLambda([HierarchyPtr, Key, StructToDisplay, InValueType]() { if(HierarchyPtr.IsValid()) { const FRigControlValue Value = FRigControlValue::Make(*(T*)StructToDisplay->GetStructMemory()); HierarchyPtr->SetControlValue(Key, Value, InValueType, true); if(InValueType == ERigControlValueType::Initial) { if(UControlRigBlueprint* Blueprint = RigElementDetails_GetBlueprintFromHierarchy(HierarchyPtr.Get())) { Blueprint->Hierarchy->SetControlValue(Key, Value, InValueType, true); } } } }); TSharedPtr Handle = Row->GetPropertyHandle(); Handle->SetOnPropertyValueChanged(OnStructContentsChangedDelegate); Handle->SetOnChildPropertyValueChanged(OnStructContentsChangedDelegate); } void FRigControlElementDetails_SetupValueWidget(IDetailCategoryBuilder& InCategory, ERigControlValueType InValueType, FRigControlElement* InControlElement, URigHierarchy* InHierarchy) { switch(InControlElement->Settings.ControlType) { case ERigControlType::Bool: { if((InValueType == ERigControlValueType::Minimum) || (InValueType == ERigControlValueType::Maximum)) { return; } FRigControlElementDetails_SetupBoolValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::Integer: { FRigControlElementDetails_SetupIntegerValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::Float: { FRigControlElementDetails_SetupFloatValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::Vector2D: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::Position: case ERigControlType::Scale: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::Rotator: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::TransformNoScale: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::EulerTransform: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } case ERigControlType::Transform: { FRigControlElementDetails_SetupStructValueWidget(InCategory, InValueType, InControlElement, InHierarchy); break; } default: { ensure(false); break; } } } TArray> FRigControlElementDetails::ControlTypeList; void FRigControlElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigTransformElementDetails::CustomizeDetails(DetailBuilder); GizmoNameList.Reset(); if (BlueprintBeingCustomized) { if (!BlueprintBeingCustomized->GizmoLibrary.IsValid()) { BlueprintBeingCustomized->GizmoLibrary.LoadSynchronous(); } if (BlueprintBeingCustomized->GizmoLibrary.IsValid()) { GizmoNameList.Add(MakeShared(BlueprintBeingCustomized->GizmoLibrary->DefaultGizmo.GizmoName.ToString())); for (const FControlRigGizmoDefinition& Gizmo : BlueprintBeingCustomized->GizmoLibrary->Gizmos) { GizmoNameList.Add(MakeShared(Gizmo.GizmoName.ToString())); } } } if (HierarchyBeingCustomized == nullptr || !ElementKeyBeingCustomized) { return; } IDetailCategoryBuilder& ControlCategory = DetailBuilder.EditCategory(TEXT("Control"), LOCTEXT("ControlCategory", "Control")); IDetailCategoryBuilder& LimitsCategory = DetailBuilder.EditCategory(TEXT("Limits"), LOCTEXT("LimitsCategory", "Limits")); IDetailCategoryBuilder& GizmoCategory = DetailBuilder.EditCategory(TEXT("Gizmo"), LOCTEXT("GizmoCategory", "Gizmo")); ControlCategory.InitiallyCollapsed(false); LimitsCategory.InitiallyCollapsed(false); GizmoCategory.InitiallyCollapsed(false); const TSharedRef DisplayNameHandle = DetailBuilder.GetProperty(TEXT("Settings.DisplayName")); DetailBuilder.HideProperty(DisplayNameHandle); ControlCategory.AddProperty(DisplayNameHandle).CustomWidget() .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Display Name"))) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SEditableTextBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FRigControlElementDetails::GetDisplayName) .OnTextCommitted(this, &FRigControlElementDetails::SetDisplayName, DetailBuilder.GetPropertyUtilities()) ]; if (ControlTypeList.Num() == 0) { UEnum* Enum = StaticEnum(); for (int64 Index = 0; Index < Enum->GetMaxEnumValue(); Index++) { ControlTypeList.Add(MakeShared(Enum->GetDisplayNameTextByValue(Index).ToString())); } } FRigControlElement* ControlElement = HierarchyBeingCustomized->FindChecked(ElementKeyBeingCustomized); // when control type changes, we have to refresh detail panel const TSharedRef ControlTypeHandle = DetailBuilder.GetProperty(TEXT("Settings.ControlType")); ControlTypeHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda( [this, &DetailBuilder]() { DetailBuilder.ForceRefreshDetails(); if (this->HierarchyBeingCustomized && this->ElementKeyBeingCustomized.IsValid()) { FRigControlElement* ControlElement = this->HierarchyBeingCustomized->FindChecked(ElementKeyBeingCustomized); FRigControlValue ValueToSet; 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); this->HierarchyBeingCustomized->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Current, true); 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); } } } } )); if(ControlElement->Settings.ControlType == ERigControlType::Bool) { DetailBuilder.HideCategory(TEXT("Pose")); DetailBuilder.HideProperty(TEXT("Offset")); DetailBuilder.HideProperty(TEXT("Gizmo")); } else { // setup offset transform { const TSharedRef OffsetInitialLocalTransformHandle = DetailBuilder.GetProperty(TEXT("Offset.Initial.Local.Transform")); ControlCategory.AddProperty(OffsetInitialLocalTransformHandle).DisplayName(FText::FromString(TEXT("Offset Transform"))); DetailBuilder.HideProperty(TEXT("Offset")); OffsetPropertyChain.AddHead(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Offset"))); OffsetPropertyChain.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Initial"))); OffsetPropertyChain.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Local"))); OffsetPropertyChain.AddTail(FRigComputedTransform::StaticStruct()->FindPropertyByName(TEXT("Transform"))); OffsetPropertyChain.SetActiveMemberPropertyNode(OffsetPropertyChain.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateStatic(&FRigTransformElementDetails::OnTransformChanged, &OffsetPropertyChain, BlueprintBeingCustomized); OffsetInitialLocalTransformHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); OffsetInitialLocalTransformHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); OffsetInitialLocalTransformHandle->SetToolTipText(FText::FromString(TEXT("The offset transform is used as a middle man between the parent and the local transform.\nYou can use it to offset a control for visual adjustment."))); } // setup gizmo transform { const TSharedRef GizmoInitialLocalTransformHandle = DetailBuilder.GetProperty(TEXT("Gizmo.Initial.Local.Transform")); GizmoCategory.AddProperty(GizmoInitialLocalTransformHandle).DisplayName(FText::FromString(TEXT("Gizmo Transform"))); DetailBuilder.HideProperty(TEXT("Gizmo")); GizmoPropertyChain.AddHead(FRigControlElement::StaticStruct()->FindPropertyByName(TEXT("Gizmo"))); GizmoPropertyChain.AddTail(FRigCurrentAndInitialTransform::StaticStruct()->FindPropertyByName(TEXT("Initial"))); GizmoPropertyChain.AddTail(FRigLocalAndGlobalTransform::StaticStruct()->FindPropertyByName(TEXT("Local"))); GizmoPropertyChain.AddTail(FRigComputedTransform::StaticStruct()->FindPropertyByName(TEXT("Transform"))); GizmoPropertyChain.SetActiveMemberPropertyNode(GizmoPropertyChain.GetTail()->GetValue()); const FSimpleDelegate OnTransformChangedDelegate = FSimpleDelegate::CreateStatic(&FRigTransformElementDetails::OnTransformChanged, &GizmoPropertyChain, BlueprintBeingCustomized); GizmoInitialLocalTransformHandle->SetOnPropertyValueChanged(OnTransformChangedDelegate); GizmoInitialLocalTransformHandle->SetOnChildPropertyValueChanged(OnTransformChangedDelegate); GizmoInitialLocalTransformHandle->SetToolTipText(FText::FromString(TEXT("The gizmo transform is used as a transform applied only to the UI element on the screen.\nIt doesn't affect animation."))); } } FRigControlElementDetails_SetupValueWidget(ControlCategory, ERigControlValueType::Current, ControlElement, HierarchyBeingCustomized); switch (ControlElement->Settings.ControlType) { case ERigControlType::Bool: case ERigControlType::Float: case ERigControlType::Integer: case ERigControlType::Vector2D: { FRigControlElementDetails_SetupValueWidget(ControlCategory, ERigControlValueType::Initial, ControlElement, HierarchyBeingCustomized); break; } default: { break; } } FRigControlElementDetails_SetupValueWidget(LimitsCategory, ERigControlValueType::Minimum, ControlElement, HierarchyBeingCustomized); FRigControlElementDetails_SetupValueWidget(LimitsCategory, ERigControlValueType::Maximum, ControlElement, HierarchyBeingCustomized); switch (ControlElement->Settings.ControlType) { case ERigControlType::Float: case ERigControlType::Integer: case ERigControlType::Vector2D: case ERigControlType::Position: case ERigControlType::Scale: case ERigControlType::Rotator: case ERigControlType::Transform: case ERigControlType::TransformNoScale: case ERigControlType::EulerTransform: { const TSharedRef GizmoNameHandle = DetailBuilder.GetProperty(TEXT("Settings.GizmoName")); DetailBuilder.HideProperty(GizmoNameHandle); GizmoCategory.AddProperty(GizmoNameHandle).CustomWidget() .NameContent() [ SNew(STextBlock) .Text(FText::FromString(TEXT("Gizmo Name"))) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(this, &FRigControlElementDetails::IsGizmoEnabled) ] .ValueContent() [ SNew(SControlRigGizmoNameList, ControlElement, BlueprintBeingCustomized) .OnGetNameListContent(this, &FRigControlElementDetails::GetGizmoNameList) .IsEnabled(this, &FRigControlElementDetails::IsGizmoEnabled) ]; break; } default: { DetailBuilder.HideProperty(TEXT("Settings.bGizmoEnabled")); DetailBuilder.HideProperty(TEXT("Settings.bGizmoVisible")); DetailBuilder.HideProperty(TEXT("Settings.GizmoName")); DetailBuilder.HideProperty(TEXT("Settings.GizmoColor")); break; } } if(ControlElement->Settings.ControlType != ERigControlType::Integer) { DetailBuilder.HideProperty(TEXT("Settings.ControlEnum")); } bool bShowLimitTranslation = false; bool bShowLimitRotation = false; bool bShowLimitScale = false; switch (ControlElement->Settings.ControlType) { case ERigControlType::Float: case ERigControlType::Integer: case ERigControlType::Vector2D: case ERigControlType::Position: case ERigControlType::Transform: case ERigControlType::TransformNoScale: case ERigControlType::EulerTransform: { bShowLimitTranslation = true; break; } default: { break; } } switch (ControlElement->Settings.ControlType) { case ERigControlType::Rotator: case ERigControlType::Transform: case ERigControlType::TransformNoScale: case ERigControlType::EulerTransform: { bShowLimitRotation = true; break; } default: { break; } } switch (ControlElement->Settings.ControlType) { case ERigControlType::Scale: case ERigControlType::Transform: case ERigControlType::EulerTransform: { bShowLimitScale = true; break; } default: { break; } } if(!bShowLimitTranslation) { DetailBuilder.HideProperty(TEXT("Settings.bLimitTranslation")); } if(!bShowLimitRotation) { DetailBuilder.HideProperty(TEXT("Settings.bLimitRotation")); } if(!bShowLimitScale) { DetailBuilder.HideProperty(TEXT("Settings.bLimitScale")); } switch (ControlElement->Settings.ControlType) { case ERigControlType::Integer: case ERigControlType::Float: case ERigControlType::Vector2D: { break; } default: { DetailBuilder.HideProperty(TEXT("Settings.PrimaryAxis")); break; } } if (ControlElement->Settings.ControlType == ERigControlType::Integer) { TSharedRef ControlEnum = DetailBuilder.GetProperty(TEXT("Settings.ControlEnum")); ControlEnum->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda( [this, &DetailBuilder]() { DetailBuilder.ForceRefreshDetails(); if (this->HierarchyBeingCustomized != nullptr && this->ElementKeyBeingCustomized) { if(FRigControlElement* ControlBeingCustomized = this->HierarchyBeingCustomized->Find(ElementKeyBeingCustomized)) { 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); HierarchyBeingCustomized->SetControlValue(ControlBeingCustomized, CurrentValue, ERigControlValueType::Current); if (UControlRig* DebuggedRig = Cast(BlueprintBeingCustomized->GetObjectBeingDebugged())) { URigHierarchy* DebuggedHierarchy = DebuggedRig->GetHierarchy(); if(FRigControlElement* DebuggedControlElement = DebuggedHierarchy->Find(ElementKeyBeingCustomized)) { 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); } } } } } } )); } } FText FRigControlElementDetails::GetDisplayName() const { if (HierarchyBeingCustomized != nullptr && ElementKeyBeingCustomized) { if(FRigControlElement* ControlElement = HierarchyBeingCustomized->Find(ElementKeyBeingCustomized)) { if (ControlElement->Settings.DisplayName.IsNone()) { return FText(); } return FText::FromName(ControlElement->GetDisplayName()); } } return FText(); } void FRigControlElementDetails::SetDisplayName(const FText& InNewText, ETextCommit::Type InCommitType, const TSharedRef PropertyUtilities) { if (HierarchyBeingCustomized != nullptr && ElementKeyBeingCustomized) { if(FRigControlElement* ControlElement = HierarchyBeingCustomized->Find(ElementKeyBeingCustomized)) { if(BlueprintBeingCustomized) { BlueprintBeingCustomized->Hierarchy->Modify(); } const FString NewDisplayName = InNewText.ToString().TrimStartAndEnd(); if (NewDisplayName.IsEmpty()) { ControlElement->Settings.DisplayName = FName(NAME_None); } else { ControlElement->Settings.DisplayName = *NewDisplayName; } HierarchyBeingCustomized->Notify(ERigHierarchyNotification::ControlSettingChanged, ControlElement); if (BlueprintBeingCustomized && BlueprintBeingCustomized->Hierarchy != HierarchyBeingCustomized) { if(FRigControlElement* OtherControlElement = BlueprintBeingCustomized->Hierarchy->Find(ElementKeyBeingCustomized)) { OtherControlElement->Settings.DisplayName = ControlElement->Settings.DisplayName; BlueprintBeingCustomized->Hierarchy->Notify(ERigHierarchyNotification::ControlSettingChanged, OtherControlElement); } } } } } bool FRigControlElementDetails::IsGizmoEnabled() const { URigHierarchy* Hierarchy = HierarchyBeingCustomized; if (Hierarchy != nullptr && ElementKeyBeingCustomized) { if(FRigControlElement* ControlElement = Hierarchy->Find(ElementKeyBeingCustomized)) { return ControlElement->Settings.bGizmoEnabled; } } return false; } bool FRigControlElementDetails::IsEnabled(ERigControlValueType InValueType) const { switch (InValueType) { case ERigControlValueType::Minimum: case ERigControlValueType::Maximum: { if (HierarchyBeingCustomized != nullptr && ElementKeyBeingCustomized) { if(FRigControlElement* ControlElement = HierarchyBeingCustomized->Find(ElementKeyBeingCustomized)) { return ControlElement->Settings.bLimitTranslation || ControlElement->Settings.bLimitRotation || ControlElement->Settings.bLimitScale; } } return false; } default: { break; } } return true; } const TArray>& FRigControlElementDetails::GetGizmoNameList() const { return GizmoNameList; } const TArray>& FRigControlElementDetails::GetControlTypeList() const { return ControlTypeList; } void FRigNullElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { FRigTransformElementDetails::CustomizeDetails(DetailBuilder); } #undef LOCTEXT_NAMESPACE