Files
UnrealEngine/Engine/Plugins/Animation/ControlRig/Source/ControlRigEditor/Private/ControlRigElementDetails.cpp
2025-05-18 13:04:45 +08:00

6827 lines
215 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ControlRigElementDetails.h"
#include "Widgets/SWidget.h"
#include "IDetailChildrenBuilder.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Input/SVectorInputBox.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SExpandableArea.h"
#include "Widgets/Colors/SColorBlock.h"
#include "Widgets/Colors/SColorPicker.h"
#include "ControlRigBlueprint.h"
#include "ModularRig.h"
#include "Graph/ControlRigGraph.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditorModule.h"
#include "SEnumCombo.h"
#include "Units/Execution/RigUnit_BeginExecution.h"
#include "Units/Execution/RigUnit_DynamicHierarchy.h"
#include "Widgets/SRigVMGraphPinVariableBinding.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Styling/AppStyle.h"
#include "StructViewerFilter.h"
#include "StructViewerModule.h"
#include "Widgets/SRigVMGraphPinEnumPicker.h"
#include "IStructureDataProvider.h"
#include "Editor/ControlRigEditor.h"
#include "Editor/SRigConnectorTargetWidget.h"
#include "ModularRigRuleManager.h"
#include "Async/TaskGraphInterfaces.h"
#define LOCTEXT_NAMESPACE "ControlRigElementDetails"
static const FText ControlRigDetailsMultipleValues = LOCTEXT("MultipleValues", "Multiple Values");
struct FRigElementTransformWidgetSettings
{
FRigElementTransformWidgetSettings()
: RotationRepresentation(MakeShareable(new ESlateRotationRepresentation::Type(ESlateRotationRepresentation::Rotator)))
, IsComponentRelative(MakeShareable(new UE::Math::TVector<float>(1.f, 1.f, 1.f)))
, IsScaleLocked(TSharedPtr<bool>(new bool(false)))
{
}
TSharedPtr<ESlateRotationRepresentation::Type> RotationRepresentation;
TSharedRef<UE::Math::TVector<float>> IsComponentRelative;
TSharedPtr<bool> IsScaleLocked;
static FRigElementTransformWidgetSettings& FindOrAdd(
ERigControlValueType InValueType,
ERigTransformElementDetailsTransform::Type InTransformType,
const SAdvancedTransformInputBox<FEulerTransform>::FArguments& WidgetArgs)
{
uint32 Hash = GetTypeHash(WidgetArgs._ConstructLocation);
Hash = HashCombine(Hash, GetTypeHash(WidgetArgs._ConstructRotation));
Hash = HashCombine(Hash, GetTypeHash(WidgetArgs._ConstructScale));
Hash = HashCombine(Hash, GetTypeHash(WidgetArgs._AllowEditRotationRepresentation));
Hash = HashCombine(Hash, GetTypeHash(WidgetArgs._DisplayScaleLock));
Hash = HashCombine(Hash, GetTypeHash(InValueType));
Hash = HashCombine(Hash, GetTypeHash(InTransformType));
return sSettings.FindOrAdd(Hash);
}
static TMap<uint32, FRigElementTransformWidgetSettings> sSettings;
};
TMap<uint32, FRigElementTransformWidgetSettings> FRigElementTransformWidgetSettings::sSettings;
void RigElementKeyDetails_GetCustomizedInfo(TSharedRef<IPropertyHandle> InStructPropertyHandle, UControlRigBlueprint*& OutBlueprint)
{
TArray<UObject*> Objects;
InStructPropertyHandle->GetOuterObjects(Objects);
for (UObject* Object : Objects)
{
if(!IsValid(Object))
{
continue;
}
if (Object->IsA<UControlRigBlueprint>())
{
OutBlueprint = CastChecked<UControlRigBlueprint>(Object);
break;
}
OutBlueprint = Object->GetTypedOuter<UControlRigBlueprint>();
if(OutBlueprint)
{
break;
}
if(const UControlRig* ControlRig = Object->GetTypedOuter<UControlRig>())
{
OutBlueprint = Cast<UControlRigBlueprint>(ControlRig->GetClass()->ClassGeneratedBy);
if(OutBlueprint)
{
break;
}
}
}
if (OutBlueprint == nullptr)
{
TArray<UPackage*> Packages;
InStructPropertyHandle->GetOuterPackages(Packages);
for (UPackage* Package : Packages)
{
if (Package == nullptr)
{
continue;
}
TArray<UObject*> SubObjects;
Package->GetDefaultSubobjects(SubObjects);
for (UObject* SubObject : SubObjects)
{
if (UControlRig* Rig = Cast<UControlRig>(SubObject))
{
UControlRigBlueprint* Blueprint = Cast<UControlRigBlueprint>(Rig->GetClass()->ClassGeneratedBy);
if (Blueprint)
{
if(Blueprint->GetOutermost() == Package)
{
OutBlueprint = Blueprint;
break;
}
}
}
}
if (OutBlueprint)
{
break;
}
}
}
UControlRigGraph* RigGraph = nullptr;
if(OutBlueprint)
{
for (UEdGraph* Graph : OutBlueprint->UbergraphPages)
{
RigGraph = Cast<UControlRigGraph>(Graph);
if (RigGraph)
{
break;
}
}
}
// only allow blueprints with at least one rig graph
if (RigGraph == nullptr)
{
OutBlueprint = nullptr;
}
}
UControlRigBlueprint* RigElementDetails_GetBlueprintFromHierarchy(URigHierarchy* InHierarchy)
{
if(InHierarchy == nullptr)
{
return nullptr;
}
UControlRigBlueprint* Blueprint = InHierarchy->GetTypedOuter<UControlRigBlueprint>();
if(Blueprint == nullptr)
{
UControlRig* Rig = InHierarchy->GetTypedOuter<UControlRig>();
if(Rig)
{
Blueprint = Cast<UControlRigBlueprint>(Rig->GetClass()->ClassGeneratedBy);
}
}
return Blueprint;
}
void SRigElementKeyWidget::Construct(const FArguments& InArgs, TSharedPtr<IPropertyHandle> InNameHandle, TSharedPtr<IPropertyHandle> InTypeHandle)
{
NameHandle = InNameHandle;
TypeHandle = InTypeHandle;
Construct(InArgs);
}
void SRigElementKeyWidget::Construct(const FArguments& InArgs)
{
BlueprintBeingCustomized = InArgs._Blueprint;
OnGetElementType = InArgs._OnGetElementType;
OnElementNameChanged = InArgs._OnElementNameChanged;
OnElementTypeChanged = InArgs._OnElementTypeChanged;
UpdateElementNameList();
TWeakPtr<SRigElementKeyWidget> WeakThisPtr = StaticCastWeakPtr<SRigElementKeyWidget>(AsWeak());
ChildSlot
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
TypeHandle.IsValid() ?
TypeHandle->CreatePropertyValueWidget()
:
SNew(SEnumComboBox, StaticEnum<ERigElementType>())
.CurrentValue_Lambda([WeakThisPtr]()
{
if(const TSharedPtr<SRigElementKeyWidget> StrongThisPtr = WeakThisPtr.Pin())
{
if (StrongThisPtr.IsValid() && StrongThisPtr->OnGetElementType.IsBound())
{
return (int32)StrongThisPtr->OnGetElementType.Execute();
}
}
return (int32)ERigElementType::None;
})
.OnEnumSelectionChanged_Lambda([WeakThisPtr](int32 InEnumValue, ESelectInfo::Type SelectInfo)
{
if(const TSharedPtr<SRigElementKeyWidget> StrongThisPtr = WeakThisPtr.Pin())
{
if(StrongThisPtr.IsValid())
{
ERigElementType EnumValue = static_cast<ERigElementType>(InEnumValue);
StrongThisPtr->OnElementTypeChanged.ExecuteIfBound(EnumValue);
StrongThisPtr->UpdateElementNameList();
StrongThisPtr->SearchableComboBox->ClearSelection();
StrongThisPtr->OnElementNameChanged.ExecuteIfBound(nullptr, ESelectInfo::Direct);
}
}
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.f, 0.f, 0.f, 0.f)
[
SAssignNew(SearchableComboBox, SSearchableComboBox)
.OptionsSource(&ElementNameList)
.OnSelectionChanged(InArgs._OnElementNameChanged)
.OnGenerateWidget_Lambda([](TSharedPtr<FString> InItem)
{
return SNew(STextBlock)
.Text(FText::FromString(InItem.IsValid() ? *InItem : FString()))
.Font(IDetailLayoutBuilder::GetDetailFont());
})
.Content()
[
SNew(STextBlock)
.Text_Lambda([InArgs]()
{
if (InArgs._OnGetElementNameAsText.IsBound())
{
return InArgs._OnGetElementNameAsText.Execute();
}
return FText();
})
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
// Use button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(1,0)
.VAlign(VAlign_Center)
[
SAssignNew(UseSelectedButton, SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ButtonColorAndOpacity_Lambda([this, InArgs]() { return UseSelectedButton.IsValid() && UseSelectedButton->IsHovered() ? InArgs._ActiveBackgroundColor : InArgs._InactiveBackgroundColor; })
.OnClicked(InArgs._OnGetSelectedClicked)
.ContentPadding(1.f)
.ToolTipText(NSLOCTEXT("ControlRigElementDetails", "ObjectGraphPin_Use_Tooltip", "Use item selected"))
[
SNew(SImage)
.ColorAndOpacity_Lambda( [this, InArgs]() { return UseSelectedButton.IsValid() && UseSelectedButton->IsHovered() ? InArgs._ActiveForegroundColor : InArgs._InactiveForegroundColor; })
.Image(FAppStyle::GetBrush("Icons.CircleArrowLeft"))
]
]
// Select in hierarchy button
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(1,0)
.VAlign(VAlign_Center)
[
SAssignNew(SelectElementButton, SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ButtonColorAndOpacity_Lambda([this, InArgs]() { return SelectElementButton.IsValid() && SelectElementButton->IsHovered() ? InArgs._ActiveBackgroundColor : InArgs._InactiveBackgroundColor; })
.OnClicked(InArgs._OnSelectInHierarchyClicked)
.ContentPadding(0)
.ToolTipText(NSLOCTEXT("ControlRigElementDetails", "ObjectGraphPin_Browse_Tooltip", "Select in hierarchy"))
[
SNew(SImage)
.ColorAndOpacity_Lambda( [this, InArgs]() { return SelectElementButton.IsValid() && SelectElementButton->IsHovered() ? InArgs._ActiveForegroundColor : InArgs._InactiveForegroundColor; })
.Image(FAppStyle::GetBrush("Icons.Search"))
]
]
];
if (TypeHandle)
{
TypeHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda(
[this, InArgs]()
{
int32 EnumValue;
TypeHandle->GetValue(EnumValue);
OnElementTypeChanged.ExecuteIfBound(static_cast<ERigElementType>(EnumValue));
UpdateElementNameList();
SearchableComboBox->ClearSelection();
OnElementNameChanged.ExecuteIfBound(nullptr, ESelectInfo::Direct);
}
));
}
}
void SRigElementKeyWidget::UpdateElementNameList()
{
ElementNameList.Reset();
if (BlueprintBeingCustomized)
{
for (UEdGraph* Graph : BlueprintBeingCustomized->UbergraphPages)
{
if (UControlRigGraph* RigGraph = Cast<UControlRigGraph>(Graph))
{
const TArray<TSharedPtr<FRigVMStringWithTag>>* NameList = nullptr;
if (OnGetElementType.IsBound())
{
NameList = RigGraph->GetElementNameList(OnGetElementType.Execute());
}
ElementNameList.Reset();
if (NameList)
{
ElementNameList.Reserve(NameList->Num());
for(const TSharedPtr<FRigVMStringWithTag>& Name : *NameList)
{
ElementNameList.Add(MakeShared<FString>(Name->GetString()));
}
}
if(SearchableComboBox.IsValid())
{
SearchableComboBox->RefreshOptions();
}
return;
}
}
}
}
void FRigElementKeyDetails::CustomizeHeader(TSharedRef<IPropertyHandle> InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
BlueprintBeingCustomized = nullptr;
RigElementKeyDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized);
TWeakPtr<FRigElementKeyDetails> WeakThisPtr = StaticCastWeakPtr<FRigElementKeyDetails>(AsWeak());
if (BlueprintBeingCustomized == nullptr)
{
HeaderRow
.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
InStructPropertyHandle->CreatePropertyValueWidget(false)
];
}
else
{
TypeHandle = InStructPropertyHandle->GetChildHandle(TEXT("Type"));
NameHandle = InStructPropertyHandle->GetChildHandle(TEXT("Name"));
HeaderRow
.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(250.f)
[
SAssignNew(RigElementKeyWidget, SRigElementKeyWidget, NameHandle, TypeHandle)
.Blueprint(BlueprintBeingCustomized)
.IsEnabled_Lambda([WeakThisPtr]()
{
if(const TSharedPtr<FRigElementKeyDetails> StrongThisPtr = WeakThisPtr.Pin())
{
if(StrongThisPtr.IsValid())
{
return !StrongThisPtr->NameHandle->IsEditConst();
}
}
return false;
})
.ActiveBackgroundColor(FSlateColor(FLinearColor(1.f, 1.f, 1.f, FRigElementKeyDetailsDefs::ActivePinBackgroundAlpha)))
.ActiveForegroundColor(FSlateColor(FLinearColor(1.f, 1.f, 1.f, FRigElementKeyDetailsDefs::ActivePinForegroundAlpha)))
.InactiveBackgroundColor(FSlateColor(FLinearColor(1.f, 1.f, 1.f, FRigElementKeyDetailsDefs::InactivePinBackgroundAlpha)))
.InactiveForegroundColor(FSlateColor(FLinearColor(1.f, 1.f, 1.f, FRigElementKeyDetailsDefs::InactivePinForegroundAlpha)))
.OnElementNameChanged(this, &FRigElementKeyDetails::OnElementNameChanged)
.OnGetSelectedClicked(this, &FRigElementKeyDetails::OnGetSelectedClicked)
//.OnGetElementNameWidget(this, &FRigElementKeyDetails::OnGetElementNameWidget)
.OnSelectInHierarchyClicked(this, &FRigElementKeyDetails::OnSelectInHierarchyClicked)
.OnGetElementNameAsText_Raw(this, &FRigElementKeyDetails::GetElementNameAsText)
.OnGetElementType(this, &FRigElementKeyDetails::GetElementType)
];
}
}
void FRigElementKeyDetails::CustomizeChildren(TSharedRef<IPropertyHandle> 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);
// if this is nested below a connection rule
const TSharedPtr<IPropertyHandle> KeyHandle = NameHandle->GetParentHandle();
if(KeyHandle.IsValid())
{
const TSharedPtr<IPropertyHandle> ParentHandle = KeyHandle->GetParentHandle();
if(ParentHandle.IsValid())
{
if (const TSharedPtr<IPropertyHandleStruct> StructPropertyHandle = ParentHandle->AsStruct())
{
if(const UScriptStruct* RuleStruct = Cast<UScriptStruct>(StructPropertyHandle->GetStructData()->GetStruct()))
{
if (RuleStruct->IsChildOf(FRigConnectionRule::StaticStruct()))
{
const void* RuleMemory = StructPropertyHandle->GetStructData()->GetStructMemory();
FString RuleContent;
RuleStruct->ExportText(RuleContent, RuleMemory, RuleMemory, nullptr, PPF_None, nullptr);
const TSharedPtr<IPropertyHandle> RuleStashHandle = ParentHandle->GetParentHandle();
FRigConnectionRuleStash Stash;
Stash.ScriptStructPath = RuleStruct->GetPathName();
Stash.ExportedText = RuleContent;
FString StashContent;
FRigConnectionRuleStash::StaticStruct()->ExportText(StashContent, &Stash, &Stash, nullptr, PPF_None, nullptr);
TArray<UObject*> Objects;
RuleStashHandle->GetOuterObjects(Objects);
FString FirstObjectValue;
for (int32 Index = 0; Index < Objects.Num(); Index++)
{
(void)RuleStashHandle->SetPerObjectValue(Index, StashContent, EPropertyValueSetFlags::DefaultFlags);
}
}
}
}
}
}
}
}
void FRigElementKeyDetails::OnElementNameChanged(TSharedPtr<FString> InItem, ESelectInfo::Type InSelectionInfo)
{
if (InItem.IsValid())
{
SetElementName(*InItem);
}
else
{
SetElementName(FString());
}
}
FText FRigElementKeyDetails::GetElementNameAsText() const
{
return FText::FromString(GetElementName());
}
FSlateColor FRigElementKeyDetails::OnGetWidgetForeground(const TSharedPtr<SButton> Button)
{
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<SButton> Button)
{
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<FRigElementKey>& 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 FRigComponentKeyDetails::CustomizeHeader(TSharedRef<IPropertyHandle> InStructPropertyHandle, FDetailWidgetRow& HeaderRow,
IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
BlueprintBeingCustomized = nullptr;
RigElementKeyDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized);
HeaderRow
.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
];
if (BlueprintBeingCustomized == nullptr)
{
HeaderRow
.ValueContent()
[
InStructPropertyHandle->CreatePropertyValueWidget(false)
];
}
}
void FRigComponentKeyDetails::CustomizeChildren(TSharedRef<IPropertyHandle> InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder,
IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
if (InStructPropertyHandle->IsValidHandle())
{
BlueprintBeingCustomized = nullptr;
RigElementKeyDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized);
ElementKeyHandle = InStructPropertyHandle->GetChildHandle(TEXT("ElementKey"));
NameHandle = InStructPropertyHandle->GetChildHandle(TEXT("Name"));
// only fill the children if the blueprint cannot be found
if (BlueprintBeingCustomized == nullptr || !ElementKeyHandle.IsValid() || !NameHandle.IsValid())
{
uint32 NumChildren = 0;
InStructPropertyHandle->GetNumChildren(NumChildren);
for (uint32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++)
{
StructBuilder.AddProperty(InStructPropertyHandle->GetChildHandle(ChildIndex).ToSharedRef());
}
}
else
{
TWeakPtr<FRigComponentKeyDetails> WeakThisPtr = StaticCastWeakPtr<FRigComponentKeyDetails>(AsWeak());
ElementKeyHandle->SetOnChildPropertyValueChanged(FSimpleDelegate::CreateLambda([WeakThisPtr]()
{
if(const TSharedPtr<FRigComponentKeyDetails> StrongThisPtr = WeakThisPtr.Pin())
{
if(StrongThisPtr.IsValid())
{
StrongThisPtr->UpdateComponentNameList();
}
}
}));
StructBuilder
.AddProperty(ElementKeyHandle.ToSharedRef());
StructBuilder
.AddProperty(NameHandle.ToSharedRef())
.CustomWidget()
.NameContent()
[
NameHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.f, 0.f, 0.f, 0.f)
[
SAssignNew(SearchableComboBox, SSearchableComboBox)
.OptionsSource(&ComponentNameList)
.OnSelectionChanged(this, &FRigComponentKeyDetails::OnComponentNameChanged)
.OnGenerateWidget_Lambda([](TSharedPtr<FString> InItem)
{
return SNew(STextBlock)
.Text(FText::FromString(InItem.IsValid() ? *InItem : FString()))
.Font(IDetailLayoutBuilder::GetDetailFont());
})
.Content()
[
SNew(STextBlock)
.Text(this, &FRigComponentKeyDetails::GetComponentNameAsText)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
];
}
}
UpdateComponentNameList();
}
FRigElementKey FRigComponentKeyDetails::GetElementKey() const
{
FRigElementKey ElementKey;
if (ElementKeyHandle.IsValid())
{
void* ElementKeyData = nullptr;
if(ElementKeyHandle->GetValueData(ElementKeyData) == FPropertyAccess::Success)
{
ElementKey = *static_cast<const FRigElementKey*>(ElementKeyData);
}
}
return ElementKey;
}
FString FRigComponentKeyDetails::GetComponentName() 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 FRigComponentKeyDetails::SetComponentName(FString InName)
{
if (NameHandle.IsValid())
{
NameHandle->SetValue(InName);
}
}
void FRigComponentKeyDetails::OnComponentNameChanged(TSharedPtr<FString> InItem, ESelectInfo::Type InSelectionInfo)
{
if (InItem.IsValid())
{
SetComponentName(*InItem);
}
else
{
SetComponentName(FString());
}
}
FText FRigComponentKeyDetails::GetComponentNameAsText() const
{
return FText::FromString(GetComponentName());
}
void FRigComponentKeyDetails::UpdateComponentNameList()
{
if(BlueprintBeingCustomized == nullptr)
{
return;
}
const FRigElementKey ElementKey = GetElementKey();
if(!ElementKey.IsValid())
{
return;
}
URigHierarchy* Hierarchy = BlueprintBeingCustomized->Hierarchy;
if(UControlRig* RigBeingDebugged = Cast<UControlRig>(BlueprintBeingCustomized->GetObjectBeingDebugged()))
{
Hierarchy = RigBeingDebugged->GetHierarchy();
}
TArray<FRigComponentKey> ComponentKeys = Hierarchy->GetComponentKeys(ElementKey);
ComponentKeys.Sort();
ComponentNameList.Reset();
ComponentNameList.Emplace(new FString(FName(NAME_None).ToString()));
for(const FRigComponentKey& ComponentKey : ComponentKeys)
{
ComponentNameList.Emplace(new FString(ComponentKey.Name.ToString()));
}
if(SearchableComboBox.IsValid())
{
SearchableComboBox->RefreshOptions();
}
}
void FRigComputedTransformDetails::CustomizeHeader(TSharedRef<IPropertyHandle> InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
BlueprintBeingCustomized = nullptr;
RigElementKeyDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized);
}
void FRigComputedTransformDetails::CustomizeChildren(TSharedRef<IPropertyHandle> 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 FRigControlTransformChannelDetails::CustomizeHeader(TSharedRef<IPropertyHandle> InStructPropertyHandle,
FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
Handle = InStructPropertyHandle;
TArray<int32> VisibleEnumValues;
const TArray<ERigControlTransformChannel>* VisibleChannels = nullptr;
// loop for controls to figure out the control type
TArray<UObject*> Objects;
InStructPropertyHandle->GetOuterObjects(Objects);
for (UObject* Object : Objects)
{
if (const URigVMDetailsViewWrapperObject* WrapperObject = Cast<URigVMDetailsViewWrapperObject>(Object))
{
if(WrapperObject->GetWrappedStruct() == FRigControlElement::StaticStruct())
{
const FRigControlElement ControlElement = WrapperObject->GetContent<FRigControlElement>();
VisibleChannels = GetVisibleChannelsForControlType(ControlElement.Settings.ControlType);
break;
}
if (const URigVMUnitNode* UnitNode = Cast<URigVMUnitNode>(WrapperObject->GetOuter()))
{
if(UnitNode->GetScriptStruct() && UnitNode->GetScriptStruct()->IsChildOf(FRigUnit_HierarchyAddControlElement::StaticStruct()))
{
FStructOnScope StructOnScope(UnitNode->GetScriptStruct());
WrapperObject->GetContent(StructOnScope.GetStructMemory(), StructOnScope.GetStruct());
const FRigUnit_HierarchyAddControlElement* RigUnit = (const FRigUnit_HierarchyAddControlElement*)StructOnScope.GetStructMemory();
VisibleChannels = GetVisibleChannelsForControlType(RigUnit->GetControlTypeToSpawn());
break;
}
}
}
}
if(VisibleChannels)
{
VisibleEnumValues.Reserve(VisibleChannels->Num());
for(int32 Index=0; Index < VisibleChannels->Num(); Index++)
{
VisibleEnumValues.Add((int32)(*VisibleChannels)[Index]);
}
}
HeaderRow
.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SEnumComboBox, StaticEnum<ERigControlTransformChannel>())
.CurrentValue_Raw(this, &FRigControlTransformChannelDetails::GetChannelAsInt32)
.OnEnumSelectionChanged_Raw(this, &FRigControlTransformChannelDetails::OnChannelChanged)
.Font(FAppStyle::GetFontStyle(TEXT("MenuItem.Font")))
.EnumValueSubset(VisibleEnumValues)
];
}
void FRigControlTransformChannelDetails::CustomizeChildren(TSharedRef<IPropertyHandle> InStructPropertyHandle,
IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
// nothing to do here
}
ERigControlTransformChannel FRigControlTransformChannelDetails::GetChannel() const
{
uint8 Value = 0;
Handle->GetValue(Value);
return (ERigControlTransformChannel)Value;
}
void FRigControlTransformChannelDetails::OnChannelChanged(int32 NewSelection, ESelectInfo::Type InSelectionInfo)
{
Handle->SetValue((uint8)NewSelection);
}
const TArray<ERigControlTransformChannel>* FRigControlTransformChannelDetails::GetVisibleChannelsForControlType(ERigControlType InControlType)
{
switch(InControlType)
{
case ERigControlType::Position:
{
static const TArray<ERigControlTransformChannel> PositionChannels = {
ERigControlTransformChannel::TranslationX,
ERigControlTransformChannel::TranslationY,
ERigControlTransformChannel::TranslationZ
};
return &PositionChannels;
}
case ERigControlType::Rotator:
{
static const TArray<ERigControlTransformChannel> RotatorChannels = {
ERigControlTransformChannel::Pitch,
ERigControlTransformChannel::Yaw,
ERigControlTransformChannel::Roll
};
return &RotatorChannels;
}
case ERigControlType::Scale:
{
static const TArray<ERigControlTransformChannel> ScaleChannels = {
ERigControlTransformChannel::ScaleX,
ERigControlTransformChannel::ScaleY,
ERigControlTransformChannel::ScaleZ
};
return &ScaleChannels;
}
case ERigControlType::Vector2D:
{
static const TArray<ERigControlTransformChannel> Vector2DChannels = {
ERigControlTransformChannel::TranslationX,
ERigControlTransformChannel::TranslationY
};
return &Vector2DChannels;
}
case ERigControlType::EulerTransform:
{
static const TArray<ERigControlTransformChannel> EulerTransformChannels = {
ERigControlTransformChannel::TranslationX,
ERigControlTransformChannel::TranslationY,
ERigControlTransformChannel::TranslationZ,
ERigControlTransformChannel::Pitch,
ERigControlTransformChannel::Yaw,
ERigControlTransformChannel::Roll,
ERigControlTransformChannel::ScaleX,
ERigControlTransformChannel::ScaleY,
ERigControlTransformChannel::ScaleZ
};
return &EulerTransformChannels;
}
default:
{
break;
}
}
return nullptr;
}
void FRigBaseElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
PerElementInfos.Reset();
TArray<TWeakObjectPtr<UObject>> DetailObjects;
DetailBuilder.GetObjectsBeingCustomized(DetailObjects);
for(TWeakObjectPtr<UObject> DetailObject : DetailObjects)
{
URigVMDetailsViewWrapperObject* WrapperObject = CastChecked<URigVMDetailsViewWrapperObject>(DetailObject.Get());
const FRigElementKey Key = WrapperObject->GetContent<FRigBaseElement>().GetKey();
FPerElementInfo Info;
Info.WrapperObject = WrapperObject;
if (const URigHierarchy* Hierarchy = Cast<URigHierarchy>(WrapperObject->GetSubject()))
{
Info.Element = Hierarchy->GetHandle(Key);
}
if(!Info.Element.IsValid())
{
return;
}
if(const UControlRigBlueprint* Blueprint = Info.GetBlueprint())
{
Info.DefaultElement = Blueprint->Hierarchy->GetHandle(Key);
}
PerElementInfos.Add(Info);
}
IDetailCategoryBuilder& GeneralCategory = DetailBuilder.EditCategory(TEXT("General"), LOCTEXT("General", "General"));
const bool bIsProcedural = IsAnyElementProcedural();
if(bIsProcedural)
{
GeneralCategory.AddCustomRow(LOCTEXT("ProceduralElement", "ProceduralElement")).WholeRowContent()
[
SNew(STextBlock)
.Text(LOCTEXT("ProceduralElementNote", "This item has been created procedurally."))
.ToolTipText(LOCTEXT("ProceduralElementTooltip", "You cannot edit the values of the item here.\nPlease change the settings on the node\nthat created the item."))
.Font(IDetailLayoutBuilder::GetDetailFont())
.ColorAndOpacity(FLinearColor::Red)
];
}
const bool bAllControls = !IsAnyElementNotOfType(ERigElementType::Control);
const bool bAllAnimationChannels = bAllControls && !IsAnyControlNotOfAnimationType(ERigControlAnimationType::AnimationChannel);
if(bAllControls && bAllAnimationChannels)
{
GeneralCategory.AddCustomRow(FText::FromString(TEXT("Parent Control")))
.NameContent()
[
SNew(SInlineEditableTextBlock)
.Text(FText::FromString(TEXT("Parent Control")))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(false)
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SNew(SEditableTextBox)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FRigBaseElementDetails::GetParentElementName)
.IsEnabled(false)
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
SAssignNew(SelectParentElementButton, SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.ButtonColorAndOpacity_Lambda([this]() { return FRigElementKeyDetails::OnGetWidgetBackground(SelectParentElementButton); })
.OnClicked(this, &FRigBaseElementDetails::OnSelectParentElementInHierarchyClicked)
.ContentPadding(0)
.ToolTipText(NSLOCTEXT("ControlRigElementDetails", "SelectParentInHierarchyToolTip", "Select Parent in hierarchy"))
[
SNew(SImage)
.ColorAndOpacity_Lambda( [this]() { return FRigElementKeyDetails::OnGetWidgetForeground(SelectParentElementButton); })
.Image(FAppStyle::GetBrush("Icons.Search"))
]
]
];
}
DetailBuilder.HideCategory(TEXT("RigElement"));
if(!bAllControls || !bAllAnimationChannels)
{
GeneralCategory.AddCustomRow(FText::FromString(TEXT("Name")))
.IsEnabled(!bIsProcedural)
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Name")))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SInlineEditableTextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FRigBaseElementDetails::GetName)
.OnTextCommitted(this, &FRigBaseElementDetails::SetName)
.OnVerifyTextChanged(this, &FRigBaseElementDetails::OnVerifyNameChanged)
.IsEnabled(!bIsProcedural && PerElementInfos.Num() == 1 && !bAllAnimationChannels)
];
}
// if we are not a bone, control or null
if(!IsAnyElementOfType(ERigElementType::Bone) &&
!IsAnyElementOfType(ERigElementType::Control) &&
!IsAnyElementOfType(ERigElementType::Null) &&
!IsAnyElementOfType(ERigElementType::Connector) &&
!IsAnyElementOfType(ERigElementType::Socket))
{
CustomizeComponents(DetailBuilder);
CustomizeMetadata(DetailBuilder);
}
}
void FRigBaseElementDetails::PendingDelete()
{
if (MetadataHandle.IsValid())
{
for (const FPerElementInfo& Info : PerElementInfos)
{
// We do not check Info.IsValid here, because even if the element
// doesn't exist anymore in the hierarchy, we still want to detach the
// metadata handle from the hierarchy
if (URigHierarchy* Hierarchy = Info.GetHierarchy())
{
if (Hierarchy->OnMetadataChanged().Remove(MetadataHandle))
{
break;
}
}
}
MetadataHandle.Reset();
}
IDetailCustomization::PendingDelete();
}
FRigElementKey FRigBaseElementDetails::GetElementKey() const
{
check(PerElementInfos.Num() == 1);
if (FRigBaseElement* Element = PerElementInfos[0].GetElement())
{
return Element->GetKey();
}
return FRigElementKey();
}
FText FRigBaseElementDetails::GetName() const
{
if(PerElementInfos.Num() > 1)
{
return ControlRigDetailsMultipleValues;
}
return FText::FromName(GetElementKey().Name);
}
FText FRigBaseElementDetails::GetParentElementName() const
{
if(PerElementInfos.Num() > 1)
{
return ControlRigDetailsMultipleValues;
}
return FText::FromName(PerElementInfos[0].GetHierarchy()->GetFirstParent(GetElementKey()).Name);
}
void FRigBaseElementDetails::SetName(const FText& InNewText, ETextCommit::Type InCommitType)
{
if(InCommitType == ETextCommit::OnCleared)
{
return;
}
if(PerElementInfos.Num() > 1)
{
return;
}
if(PerElementInfos[0].IsProcedural())
{
return;
}
if (URigHierarchy* Hierarchy = PerElementInfos[0].GetDefaultHierarchy())
{
BeginDestroy();
URigHierarchyController* Controller = Hierarchy->GetController(true);
check(Controller);
const FRigElementKey NewKey = Controller->RenameElement(GetElementKey(), *InNewText.ToString(), true, true);
if(NewKey.IsValid())
{
Controller->SelectElement(NewKey, true, true);
}
}
}
bool FRigBaseElementDetails::OnVerifyNameChanged(const FText& InText, FText& OutErrorMessage)
{
if(PerElementInfos.Num() > 1)
{
return false;
}
if(PerElementInfos[0].IsProcedural())
{
return false;
}
const URigHierarchy* Hierarchy = PerElementInfos[0].GetDefaultHierarchy();
if (Hierarchy == nullptr)
{
return false;
}
if (GetElementKey().Name.ToString() == InText.ToString())
{
return true;
}
FString OutErrorMessageStr;
if (!Hierarchy->IsNameAvailable(FRigName(InText.ToString()), GetElementKey().Type, &OutErrorMessageStr))
{
OutErrorMessage = FText::FromString(OutErrorMessageStr);
return false;
}
return true;
}
void FRigBaseElementDetails::OnStructContentsChanged(FProperty* InProperty, const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
const FPropertyChangedEvent ChangeEvent(InProperty, EPropertyChangeType::ValueSet);
PropertyUtilities->NotifyFinishedChangingProperties(ChangeEvent);
}
bool FRigBaseElementDetails::IsConstructionModeEnabled() const
{
if(PerElementInfos.IsEmpty())
{
return false;
}
if(const UControlRigBlueprint* Blueprint = PerElementInfos[0].GetBlueprint())
{
if (const UControlRig* DebuggedRig = Cast<UControlRig>(Blueprint ->GetObjectBeingDebugged()))
{
return DebuggedRig->IsConstructionModeEnabled();
}
}
return false;
}
TArray<FRigElementKey> FRigBaseElementDetails::GetElementKeys() const
{
TArray<FRigElementKey> Keys;
Algo::Transform(PerElementInfos, Keys, [](const FPerElementInfo& Info)
{
return Info.GetElement()->GetKey();
});
return Keys;
}
const FRigBaseElementDetails::FPerElementInfo& FRigBaseElementDetails::FindElement(const FRigElementKey& InKey) const
{
const FPerElementInfo* Info = FindElementByPredicate([InKey](const FPerElementInfo& Info)
{
return Info.GetElement()->GetKey() == InKey;
});
if(Info)
{
return *Info;
}
static const FPerElementInfo EmptyInfo;
return EmptyInfo;
}
bool FRigBaseElementDetails::IsAnyElementOfType(ERigElementType InType) const
{
return ContainsElementByPredicate([InType](const FPerElementInfo& Info)
{
return Info.GetElement()->GetType() == InType;
});
}
bool FRigBaseElementDetails::IsAnyElementNotOfType(ERigElementType InType) const
{
return ContainsElementByPredicate([InType](const FPerElementInfo& Info)
{
return Info.GetElement()->GetType() != InType;
});
}
bool FRigBaseElementDetails::IsAnyControlOfAnimationType(ERigControlAnimationType InType) const
{
return ContainsElementByPredicate([InType](const FPerElementInfo& Info)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return ControlElement->Settings.AnimationType == InType;
}
return false;
});
}
bool FRigBaseElementDetails::IsAnyControlNotOfAnimationType(ERigControlAnimationType InType) const
{
return ContainsElementByPredicate([InType](const FPerElementInfo& Info)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return ControlElement->Settings.AnimationType != InType;
}
return false;
});
}
bool FRigBaseElementDetails::IsAnyControlOfValueType(ERigControlType InType) const
{
return ContainsElementByPredicate([InType](const FPerElementInfo& Info)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return ControlElement->Settings.ControlType == InType;
}
return false;
});
}
bool FRigBaseElementDetails::IsAnyControlNotOfValueType(ERigControlType InType) const
{
return ContainsElementByPredicate([InType](const FPerElementInfo& Info)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return ControlElement->Settings.ControlType != InType;
}
return false;
});
}
bool FRigBaseElementDetails::IsAnyElementProcedural() const
{
return ContainsElementByPredicate([](const FPerElementInfo& Info)
{
return Info.IsProcedural();
});
}
bool FRigBaseElementDetails::IsAnyConnectorImported() const
{
return ContainsElementByPredicate([](const FPerElementInfo& Info)
{
return Info.Element.GetKey().Name.ToString().Contains(FRigHierarchyModulePath::ModuleNameSuffix);
});
}
bool FRigBaseElementDetails::IsAnyConnectorPrimary() const
{
return ContainsElementByPredicate([](const FPerElementInfo& Info)
{
if(const FRigConnectorElement* Connector = Info.Element.Get<FRigConnectorElement>())
{
return Connector->IsPrimary();
}
return false;
});
}
bool FRigBaseElementDetails::GetCommonElementType(ERigElementType& OutElementType) const
{
OutElementType = ERigElementType::None;
for(const FPerElementInfo& Info : PerElementInfos)
{
const FRigElementKey& Key = Info.Element.GetKey();
if(Key.IsValid())
{
if(OutElementType == ERigElementType::None)
{
OutElementType = Key.Type;
}
else if(OutElementType != Key.Type)
{
OutElementType = ERigElementType::None;
break;
}
}
}
return OutElementType != ERigElementType::None;
}
bool FRigBaseElementDetails::GetCommonControlType(ERigControlType& OutControlType) const
{
OutControlType = ERigControlType::Bool;
ERigElementType ElementType = ERigElementType::None;
if(GetCommonElementType(ElementType))
{
if(ElementType == ERigElementType::Control)
{
bool bSuccess = false;
for(const FPerElementInfo& Info : PerElementInfos)
{
if(const FRigControlElement* ControlElement = Info.Element.Get<FRigControlElement>())
{
if(!bSuccess)
{
OutControlType = ControlElement->Settings.ControlType;
bSuccess = true;
}
else if(OutControlType != ControlElement->Settings.ControlType)
{
OutControlType = ERigControlType::Bool;
bSuccess = false;
break;
}
}
}
return bSuccess;
}
}
return false;
}
bool FRigBaseElementDetails::GetCommonAnimationType(ERigControlAnimationType& OutAnimationType) const
{
OutAnimationType = ERigControlAnimationType::AnimationControl;
ERigElementType ElementType = ERigElementType::None;
if(GetCommonElementType(ElementType))
{
if(ElementType == ERigElementType::Control)
{
bool bSuccess = false;
for(const FPerElementInfo& Info : PerElementInfos)
{
if(const FRigControlElement* ControlElement = Info.Element.Get<FRigControlElement>())
{
if(!bSuccess)
{
OutAnimationType = ControlElement->Settings.AnimationType;
bSuccess = true;
}
else if(OutAnimationType != ControlElement->Settings.AnimationType)
{
OutAnimationType = ERigControlAnimationType::AnimationControl;
bSuccess = false;
break;
}
}
}
return bSuccess;
}
}
return false;
}
const FRigBaseElementDetails::FPerElementInfo* FRigBaseElementDetails::FindElementByPredicate(const TFunction<bool(const FPerElementInfo&)>& InPredicate) const
{
return PerElementInfos.FindByPredicate(InPredicate);
}
bool FRigBaseElementDetails::ContainsElementByPredicate(const TFunction<bool(const FPerElementInfo&)>& InPredicate) const
{
return PerElementInfos.ContainsByPredicate(InPredicate);
}
void FRigBaseElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule)
{
const URigVMDetailsViewWrapperObject* CDOWrapper = CastChecked<URigVMDetailsViewWrapperObject>(UControlRigWrapperObject::StaticClass()->GetDefaultObject());
FRigBoneElementDetails().RegisterSectionMappings(PropertyEditorModule, CDOWrapper->GetClassForStruct(FRigBoneElement::StaticStruct()));
FRigNullElementDetails().RegisterSectionMappings(PropertyEditorModule, CDOWrapper->GetClassForStruct(FRigNullElement::StaticStruct()));
FRigControlElementDetails().RegisterSectionMappings(PropertyEditorModule, CDOWrapper->GetClassForStruct(FRigControlElement::StaticStruct()));
}
void FRigBaseElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass)
{
TSharedRef<FPropertySection> MetadataSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Metadata", LOCTEXT("Metadata", "Metadata"));
MetadataSection->AddCategory("Metadata");
}
FReply FRigBaseElementDetails::OnSelectParentElementInHierarchyClicked()
{
if (PerElementInfos.Num() == 1)
{
FRigElementKey Key = GetElementKey();
if (Key.IsValid())
{
const FRigElementKey ParentKey = PerElementInfos[0].GetHierarchy()->GetFirstParent(GetElementKey());
if(ParentKey.IsValid())
{
return OnSelectElementClicked(ParentKey);
}
}
}
return FReply::Handled();
}
FReply FRigBaseElementDetails::OnSelectElementClicked(const FRigElementKey& InKey)
{
if (PerElementInfos.Num() == 1)
{
if (InKey.IsValid())
{
PerElementInfos[0].GetHierarchy()->GetController(true)->SetSelection({InKey});
}
}
return FReply::Handled();
}
class FRigComponentStructProvider : public IStructureDataProvider
{
public:
FRigComponentStructProvider(URigHierarchy* InHierarchy)
: HierarchyPtr(InHierarchy)
{
}
virtual ~FRigComponentStructProvider() override {}
int32 Num() const
{
return ComponentIndices.Num();
}
const FRigBaseComponent* GetComponent(int32 InIndex) const
{
if(URigHierarchy* Hierarchy = GetHierarchy())
{
return Hierarchy->GetComponent(ComponentIndices[InIndex]);
}
return nullptr;
}
URigHierarchy* GetHierarchy() const
{
if(HierarchyPtr.IsValid())
{
return HierarchyPtr.Get();
}
return nullptr;
}
void Reset()
{
HierarchyPtr.Reset();
ComponentIndices.Reset();
}
void AddComponent(const FRigBaseComponent* InComponent)
{
ComponentIndices.AddUnique(InComponent->GetIndexInHierarchy());
}
virtual bool IsValid() const override
{
return GetHierarchy() && Num() != 0;
}
virtual const UStruct* GetBaseStructure() const override
{
if(Num() > 0)
{
if(const FRigBaseComponent* Component = GetComponent(0))
{
return Component->GetScriptStruct();
}
}
return nullptr;
}
virtual void GetInstances(TArray<TSharedPtr<FStructOnScope>>& OutInstances, const UStruct* ExpectedBaseStructure) const override
{
for(int32 Index = 0; Index < Num(); Index++)
{
if(const FRigBaseComponent* Component = GetComponent(Index))
{
check(Component->GetScriptStruct() == ExpectedBaseStructure);
uint8* Memory = reinterpret_cast<uint8*>(const_cast<FRigBaseComponent*>(Component));
OutInstances.Add(MakeShareable(new FStructOnScope(ExpectedBaseStructure, Memory)));
}
}
}
virtual bool IsPropertyIndirection() const override
{
return false;
}
protected:
mutable TWeakObjectPtr<URigHierarchy> HierarchyPtr;
TArray<int32> ComponentIndices;
};
void FRigBaseElementDetails::CustomizeComponents(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
if(Hierarchy == nullptr)
{
return;
}
if(const FRigBaseElement* Element = PerElementInfos[0].GetElement<>())
{
for(int32 ComponentIndex = 0; ComponentIndex < Element->NumComponents(); ComponentIndex++)
{
if(const FRigBaseComponent* Component = Element->GetComponent(ComponentIndex))
{
FSharedComponent SharedComponent;
SharedComponent.Handle = FRigComponentHandle(Hierarchy, Component);
SharedComponent.ScriptStruct = Component->GetScriptStruct();
SharedComponent.bIsProcedural = Component->IsProcedural();
SharedComponents.Add(SharedComponent);
}
}
}
for(int32 ElementIndex = 1; ElementIndex < PerElementInfos.Num(); ElementIndex++)
{
if(const FRigBaseElement* Element = PerElementInfos[ElementIndex].GetElement<>())
{
// remove any missing or type-mismatching component from the list to display
SharedComponents.RemoveAll([Element, Hierarchy](const FSharedComponent& SharedComponent) -> bool {
const FRigComponentKey ComponentKey(Element->GetKey(), SharedComponent.Handle.GetComponentName());
if(const FRigBaseComponent* Component = Hierarchy->FindComponent(ComponentKey))
{
return Component->GetScriptStruct() != SharedComponent.ScriptStruct;
}
return true;
});
// update the procedural flag in case any component is procedural
for(FSharedComponent& SharedComponent : SharedComponents)
{
if(SharedComponent.bIsProcedural)
{
continue;
}
if(const FRigBaseComponent* Component = Element->FindComponent(SharedComponent.Handle.GetComponentName()))
{
if(Component->IsProcedural())
{
SharedComponent.bIsProcedural = true;
}
}
}
}
}
if(SharedComponents.IsEmpty())
{
return;
}
for(FSharedComponent& SharedComponent : SharedComponents)
{
TSharedPtr<FRigComponentStructProvider> StructProvider = MakeShareable(new FRigComponentStructProvider(Hierarchy));
for(int32 ElementIndex = 0; ElementIndex < PerElementInfos.Num(); ElementIndex++)
{
const FRigComponentKey ComponentKey(PerElementInfos[ElementIndex].Element.GetKey(), SharedComponent.Handle.GetComponentName());
if(const FRigBaseComponent* Component = Hierarchy->FindComponent(ComponentKey))
{
StructProvider->AddComponent(Component);
}
}
if(StructProvider->Num() == 0)
{
continue;
}
const FName ComponentName = SharedComponent.Handle.GetComponentName();
const FText DisplayName = FText::Format(LOCTEXT("ComponentCategoryTitleFormat", "{0} Component"), FText::FromName(ComponentName));
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(ComponentName, DisplayName);
TArray<IDetailPropertyRow*> DetailPropertyRows;
Category.AddAllExternalStructureProperties(StructProvider, EPropertyLocation::Default, &DetailPropertyRows);
for(IDetailPropertyRow* DetailPropertyRow : DetailPropertyRows)
{
if(SharedComponent.bIsProcedural)
{
DetailPropertyRow->IsEnabled(false);
}
FSimpleDelegate OnThisOrChildPropertyChanged = FSimpleDelegate::CreateLambda([this, ComponentName]()
{
for(int32 ElementIndex = 0; ElementIndex < PerElementInfos.Num(); ElementIndex++)
{
if(URigHierarchy* Hierarchy = PerElementInfos[ElementIndex].GetHierarchy())
{
const FRigComponentKey ComponentKey(PerElementInfos[ElementIndex].Element.GetKey(), ComponentName);
if(const FRigBaseComponent* Component = Hierarchy->FindComponent(ComponentKey))
{
if(URigHierarchy* DefaultHierarchy = PerElementInfos[ElementIndex].GetDefaultHierarchy())
{
if(URigHierarchyController* Controller = DefaultHierarchy->GetController())
{
FRigComponentState State = Component->GetState();
Controller->SetComponentState(ComponentKey, State, true);
}
}
}
}
}
});
DetailPropertyRow->GetPropertyHandle()->SetOnPropertyValueChanged(OnThisOrChildPropertyChanged);
DetailPropertyRow->GetPropertyHandle()->SetOnChildPropertyValueChanged(OnThisOrChildPropertyChanged);
}
}
}
void FRigBaseElementDetails::CustomizeMetadata(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.Num() != 1)
{
return;
}
URigHierarchy* Hierarchy = nullptr;
if (!MetadataHandle.IsValid())
{
const FPerElementInfo& Info = PerElementInfos[0];
Hierarchy = Info.IsValid() ? Info.GetHierarchy() : nullptr;
if (!Hierarchy)
{
return;
}
if (UControlRigBlueprint* Blueprint = PerElementInfos[0].GetBlueprint())
{
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (IAssetEditorInstance* EditorInstance = AssetEditorSubsystem->FindEditorForAsset(Blueprint, false))
{
if (IControlRigBaseEditor* Editor = FControlRigBaseEditor::GetFromAssetEditorInstance(EditorInstance))
{
if (Editor->GetReplayPlaybackMode() != EControlRigReplayPlaybackMode::Live)
{
Hierarchy->OnMetadataChanged().Remove(MetadataHandle);
MetadataHandle.Reset();
return;
}
}
}
}
if (MetadataHandle.IsValid())
{
Hierarchy->OnMetadataChanged().Remove(MetadataHandle);
MetadataHandle.Reset();
}
TWeakPtr<IPropertyUtilities> WeakPropertyUtilities = DetailBuilder.GetPropertyUtilities().ToWeakPtr();
MetadataHandle = Hierarchy->OnMetadataChanged().AddLambda(
[this, WeakPropertyUtilities](const FRigElementKey& InKey, const FName&)
{
if(WeakPropertyUtilities.IsValid())
{
const FRigBaseElement* Element = PerElementInfos.Num() == 1 ? PerElementInfos[0].GetElement() : nullptr;
if (InKey.Type == ERigElementType::All || (Element && Element->GetKey() == InKey))
{
if(const URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy())
{
if(const UControlRig* ControlRig = Hierarchy->GetTypedOuter<UControlRig>())
{
if(ControlRig->IsConstructionModeEnabled())
{
return;
}
}
}
// run the refresh of the user interface on the next tick on the game thread
FFunctionGraphTask::CreateAndDispatchWhenReady([WeakPropertyUtilities]()
{
const TSharedPtr<IPropertyUtilities> PropertyUtilities = WeakPropertyUtilities.IsValid() ? WeakPropertyUtilities.Pin() : nullptr;
if(PropertyUtilities.IsValid())
{
PropertyUtilities->ForceRefresh();
}
}, TStatId(), NULL, ENamedThreads::GameThread);
}
}
});
}
FRigBaseElement* Element = PerElementInfos[0].Element.Get();
TArray<FName> MetadataNames = Element->GetOwner()->GetMetadataNames(Element->GetKey());
if(MetadataNames.IsEmpty())
{
return;
}
IDetailCategoryBuilder& MetadataCategory = DetailBuilder.EditCategory(TEXT("Metadata"), LOCTEXT("Metadata", "Metadata"));
for(FName MetadataName: MetadataNames)
{
FRigBaseMetadata* Metadata = Element->GetMetadata(MetadataName);
TSharedPtr<FStructOnScope> StructOnScope = MakeShareable(new FStructOnScope(Metadata->GetMetadataStruct(), reinterpret_cast<uint8*>(Metadata)));
FAddPropertyParams Params;
Params.CreateCategoryNodes(false);
Params.ForceShowProperty();
IDetailPropertyRow* Row = MetadataCategory.AddExternalStructureProperty(StructOnScope, TEXT("Value"), EPropertyLocation::Default, Params);
if(Row)
{
(*Row)
.DisplayName(FText::FromName(Metadata->GetName()))
.IsEnabled(false);
}
}
}
TSharedPtr<TArray<ERigTransformElementDetailsTransform::Type>> FRigTransformElementDetails::PickedTransforms;
void FRigTransformElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
FRigBaseElementDetails::CustomizeDetails(DetailBuilder);
}
void FRigTransformElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass)
{
FRigBaseElementDetails::RegisterSectionMappings(PropertyEditorModule, InClass);
TSharedRef<FPropertySection> TransformSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Transform", LOCTEXT("Transform", "Transform"));
TransformSection->AddCategory("General");
TransformSection->AddCategory("Value");
TransformSection->AddCategory("Transform");
}
void FRigTransformElementDetails::CustomizeTransform(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
TArray<FRigElementKey> Keys = GetElementKeys();
Keys = PerElementInfos[0].GetHierarchy()->SortKeys(Keys);
const bool bIsProcedural = IsAnyElementProcedural();
const bool bAllControls = !IsAnyElementNotOfType(ERigElementType::Control) && !IsAnyControlOfValueType(ERigControlType::Bool);;
const bool bAllAnimationChannels = !IsAnyControlNotOfAnimationType(ERigControlAnimationType::AnimationChannel);
if(bAllControls && bAllAnimationChannels)
{
return;
}
bool bShowLimits = false;
TArray<ERigTransformElementDetailsTransform::Type> TransformTypes;
TArray<FText> ButtonLabels;
TArray<FText> ButtonTooltips;
if(bAllControls)
{
TransformTypes = {
ERigTransformElementDetailsTransform::Initial,
ERigTransformElementDetailsTransform::Current,
ERigTransformElementDetailsTransform::Offset
};
ButtonLabels = {
LOCTEXT("Initial", "Initial"),
LOCTEXT("Current", "Current"),
LOCTEXT("Offset", "Offset")
};
ButtonTooltips = {
LOCTEXT("InitialTooltip", "Initial transform in the reference pose"),
LOCTEXT("CurrentTooltip", "Current animation transform"),
LOCTEXT("OffsetTooltip", "Offset transform under the control")
};
bShowLimits = !IsAnyControlNotOfValueType(ERigControlType::EulerTransform);
if(bShowLimits)
{
TransformTypes.Append({
ERigTransformElementDetailsTransform::Minimum,
ERigTransformElementDetailsTransform::Maximum
});
ButtonLabels.Append({
LOCTEXT("Min", "Min"),
LOCTEXT("Max", "Max")
});
ButtonTooltips.Append({
LOCTEXT("ValueMinimumTooltip", "The minimum limit(s) for the control"),
LOCTEXT("ValueMaximumTooltip", "The maximum limit(s) for the control")
});
}
}
else
{
TransformTypes = {
ERigTransformElementDetailsTransform::Initial,
ERigTransformElementDetailsTransform::Current
};
ButtonLabels = {
LOCTEXT("Initial", "Initial"),
LOCTEXT("Current", "Current")
};
ButtonTooltips = {
LOCTEXT("InitialTooltip", "Initial transform in the reference pose"),
LOCTEXT("CurrentTooltip", "Current animation transform")
};
}
TArray<bool> bTransformsEnabled;
// determine if the transforms are enabled
for(int32 Index = 0; Index < TransformTypes.Num(); Index++)
{
const ERigTransformElementDetailsTransform::Type CurrentTransformType = TransformTypes[Index];
bool bIsTransformEnabled = true;
if(bIsProcedural)
{
// procedural items only allow editing of the current transform
bIsTransformEnabled = CurrentTransformType == ERigTransformElementDetailsTransform::Current;
}
if(bIsTransformEnabled)
{
if (IsAnyElementOfType(ERigElementType::Control))
{
bIsTransformEnabled = IsAnyControlOfValueType(ERigControlType::EulerTransform) ||
IsAnyControlOfValueType(ERigControlType::Transform) ||
CurrentTransformType == ERigTransformElementDetailsTransform::Offset;
if(!bIsTransformEnabled)
{
ButtonTooltips[Index] = FText::FromString(
FString::Printf(TEXT("%s\n%s"),
*ButtonTooltips[Index].ToString(),
TEXT("Only transform controls can be edited here. Refer to the 'Value' section instead.")));
}
}
else if (IsAnyElementOfType(ERigElementType::Bone) && CurrentTransformType == ERigTransformElementDetailsTransform::Initial)
{
for(const FPerElementInfo& Info : PerElementInfos)
{
if(const FRigBoneElement* BoneElement = Info.GetElement<FRigBoneElement>())
{
bIsTransformEnabled = BoneElement->BoneType == ERigBoneType::User;
if(!bIsTransformEnabled)
{
ButtonTooltips[Index] = FText::FromString(
FString::Printf(TEXT("%s\n%s"),
*ButtonTooltips[Index].ToString(),
TEXT("Imported Bones' initial transform cannot be edited.")));
}
}
}
}
}
bTransformsEnabled.Add(bIsTransformEnabled);
}
if(!PickedTransforms.IsValid())
{
PickedTransforms = MakeShareable(new TArray<ERigTransformElementDetailsTransform::Type>({ERigTransformElementDetailsTransform::Current}));
}
TSharedPtr<SSegmentedControl<ERigTransformElementDetailsTransform::Type>> TransformChoiceWidget =
SSegmentedControl<ERigTransformElementDetailsTransform::Type>::Create(
TransformTypes,
ButtonLabels,
ButtonTooltips,
*PickedTransforms.Get(),
true,
SSegmentedControl<ERigTransformElementDetailsTransform::Type>::FOnValuesChanged::CreateLambda(
[](TArray<ERigTransformElementDetailsTransform::Type> NewSelection)
{
(*FRigTransformElementDetails::PickedTransforms.Get()) = NewSelection;
}
)
);
IDetailCategoryBuilder& TransformCategory = DetailBuilder.EditCategory(TEXT("Transform"), LOCTEXT("Transform", "Transform"));
AddChoiceWidgetRow(TransformCategory, FText::FromString(TEXT("TransformType")), TransformChoiceWidget.ToSharedRef());
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs = SAdvancedTransformInputBox<FEulerTransform>::FArguments()
.DisplayToggle(false)
.DisplayRelativeWorld(true)
.Font(IDetailLayoutBuilder::GetDetailFont())
.PreventThrottling(true);
for(int32 Index = 0; Index < ButtonLabels.Num(); Index++)
{
const ERigTransformElementDetailsTransform::Type CurrentTransformType = TransformTypes[Index];
ERigControlValueType CurrentValueType = ERigControlValueType::Current;
switch(CurrentTransformType)
{
case ERigTransformElementDetailsTransform::Initial:
{
CurrentValueType = ERigControlValueType::Initial;
break;
}
case ERigTransformElementDetailsTransform::Minimum:
{
CurrentValueType = ERigControlValueType::Minimum;
break;
}
case ERigTransformElementDetailsTransform::Maximum:
{
CurrentValueType = ERigControlValueType::Maximum;
break;
}
}
TransformWidgetArgs.Visibility_Lambda([TransformChoiceWidget, Index]() -> EVisibility
{
return TransformChoiceWidget->HasValue((ERigTransformElementDetailsTransform::Type)Index) ? EVisibility::Visible : EVisibility::Collapsed;
});
TransformWidgetArgs.IsEnabled(bTransformsEnabled[Index]);
CreateEulerTransformValueWidgetRow(
Keys,
TransformWidgetArgs,
TransformCategory,
ButtonLabels[Index],
ButtonTooltips[Index],
CurrentTransformType,
CurrentValueType);
}
}
bool FRigTransformElementDetails::IsCurrentLocalEnabled() const
{
return IsAnyElementOfType(ERigElementType::Control);
}
void FRigTransformElementDetails::AddChoiceWidgetRow(IDetailCategoryBuilder& InCategory, const FText& InSearchText, TSharedRef<SWidget> InWidget)
{
InCategory.AddCustomRow(FText::FromString(TEXT("TransformType")))
.ValueContent()
.MinDesiredWidth(375.f)
.MaxDesiredWidth(375.f)
.HAlign(HAlign_Left)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
[
InWidget
]
];
}
FDetailWidgetRow& FRigTransformElementDetails::CreateTransformComponentValueWidgetRow(
ERigControlType InControlType,
const TArray<FRigElementKey>& Keys,
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigTransformElementDetailsTransform::Type CurrentTransformType,
ERigControlValueType ValueType,
TSharedPtr<SWidget> NameContent)
{
TransformWidgetArgs
.Font(IDetailLayoutBuilder::GetDetailFont())
.AllowEditRotationRepresentation(false);
if(TransformWidgetArgs._DisplayRelativeWorld &&
!TransformWidgetArgs._OnGetIsComponentRelative.IsBound() &&
!TransformWidgetArgs._OnIsComponentRelativeChanged.IsBound())
{
TSharedRef<UE::Math::TVector<float>> IsComponentRelative = MakeShareable(new UE::Math::TVector<float>(1.f, 1.f, 1.f));
TransformWidgetArgs
.OnGetIsComponentRelative_Lambda(
[IsComponentRelative](ESlateTransformComponent::Type InComponent)
{
return IsComponentRelative->operator[]((int32)InComponent) > 0.f;
})
.OnIsComponentRelativeChanged_Lambda(
[IsComponentRelative](ESlateTransformComponent::Type InComponent, bool bIsRelative)
{
IsComponentRelative->operator[]((int32)InComponent) = bIsRelative ? 1.f : 0.f;
});
}
TransformWidgetArgs.ConstructLocation(InControlType == ERigControlType::Position);
TransformWidgetArgs.ConstructRotation(InControlType == ERigControlType::Rotator);
TransformWidgetArgs.ConstructScale(InControlType == ERigControlType::Scale);
return CreateEulerTransformValueWidgetRow(
Keys,
TransformWidgetArgs,
CategoryBuilder,
Label,
Tooltip,
CurrentTransformType,
ValueType,
NameContent);
}
TSharedPtr<TArray<ERigControlValueType>> FRigControlElementDetails::PickedValueTypes;
FDetailWidgetRow& FRigTransformElementDetails::CreateEulerTransformValueWidgetRow(
const TArray<FRigElementKey>& Keys,
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigTransformElementDetailsTransform::Type CurrentTransformType,
ERigControlValueType ValueType,
TSharedPtr<SWidget> NameContent)
{
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
if(ValueType == ERigControlValueType::Current)
{
HierarchyToChange = Hierarchy;
}
const FRigElementTransformWidgetSettings& Settings = FRigElementTransformWidgetSettings::FindOrAdd(ValueType, CurrentTransformType, TransformWidgetArgs);
const bool bDisplayRelativeWorldOnCurrent = TransformWidgetArgs._DisplayRelativeWorld;
if(bDisplayRelativeWorldOnCurrent &&
!TransformWidgetArgs._OnGetIsComponentRelative.IsBound() &&
!TransformWidgetArgs._OnIsComponentRelativeChanged.IsBound())
{
TSharedRef<UE::Math::TVector<float>> IsComponentRelativeStorage = Settings.IsComponentRelative;
TransformWidgetArgs.OnGetIsComponentRelative_Lambda(
[IsComponentRelativeStorage](ESlateTransformComponent::Type InComponent)
{
return IsComponentRelativeStorage->operator[]((int32)InComponent) > 0.f;
})
.OnIsComponentRelativeChanged_Lambda(
[IsComponentRelativeStorage](ESlateTransformComponent::Type InComponent, bool bIsRelative)
{
IsComponentRelativeStorage->operator[]((int32)InComponent) = bIsRelative ? 1.f : 0.f;
});
}
const TSharedPtr<ESlateRotationRepresentation::Type> RotationRepresentationStorage = Settings.RotationRepresentation;
TransformWidgetArgs.RotationRepresentation(RotationRepresentationStorage);
auto IsComponentRelative = [TransformWidgetArgs](int32 Component) -> bool
{
if(TransformWidgetArgs._OnGetIsComponentRelative.IsBound())
{
return TransformWidgetArgs._OnGetIsComponentRelative.Execute((ESlateTransformComponent::Type)Component);
}
return true;
};
auto ConformComponentRelative = [TransformWidgetArgs, IsComponentRelative](int32 Component)
{
if(TransformWidgetArgs._OnIsComponentRelativeChanged.IsBound())
{
bool bRelative = IsComponentRelative(Component);
TransformWidgetArgs._OnIsComponentRelativeChanged.Execute(ESlateTransformComponent::Location, bRelative);
TransformWidgetArgs._OnIsComponentRelativeChanged.Execute(ESlateTransformComponent::Rotation, bRelative);
TransformWidgetArgs._OnIsComponentRelativeChanged.Execute(ESlateTransformComponent::Scale, bRelative);
}
};
TransformWidgetArgs.IsScaleLocked(Settings.IsScaleLocked);
switch(CurrentTransformType)
{
case ERigTransformElementDetailsTransform::Minimum:
case ERigTransformElementDetailsTransform::Maximum:
{
TransformWidgetArgs.AllowEditRotationRepresentation(false);
TransformWidgetArgs.DisplayRelativeWorld(false);
TransformWidgetArgs.DisplayToggle(true);
TransformWidgetArgs.OnGetToggleChecked_Lambda([Keys, Hierarchy, ValueType]
(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type RotationRepresentation,
ESlateTransformSubComponent::Type SubComponent
) -> ECheckBoxState
{
TOptional<bool> FirstValue;
for(const FRigElementKey& Key : Keys)
{
if(const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
TOptional<bool> Value;
switch(ControlElement->Settings.ControlType)
{
case ERigControlType::Position:
case ERigControlType::Rotator:
case ERigControlType::Scale:
{
if(ControlElement->Settings.LimitEnabled.Num() == 3)
{
int32 Index = INDEX_NONE;
if (ControlElement->Settings.ControlType == ERigControlType::Rotator)
{
// TRotator is ordered Roll,Pitch,Yaw, while SNumericRotatorInputBox is ordered Pitch,Yaw,Roll
switch (SubComponent)
{
case ESlateTransformSubComponent::Pitch: Index = 1; break;
case ESlateTransformSubComponent::Yaw: Index = 2; break;
case ESlateTransformSubComponent::Roll: Index = 0; break;
}
}
else
{
Index = int32(SubComponent) - int32(ESlateTransformSubComponent::X);
}
if (Index != INDEX_NONE)
{
Value = ControlElement->Settings.LimitEnabled[Index].GetForValueType(ValueType);
}
}
break;
}
case ERigControlType::EulerTransform:
{
if(ControlElement->Settings.LimitEnabled.Num() == 9)
{
switch(Component)
{
case ESlateTransformComponent::Location:
{
switch(SubComponent)
{
case ESlateTransformSubComponent::X:
{
Value = ControlElement->Settings.LimitEnabled[0].GetForValueType(ValueType);
break;
}
case ESlateTransformSubComponent::Y:
{
Value = ControlElement->Settings.LimitEnabled[1].GetForValueType(ValueType);
break;
}
case ESlateTransformSubComponent::Z:
{
Value = ControlElement->Settings.LimitEnabled[2].GetForValueType(ValueType);
break;
}
default:
{
break;
}
}
break;
}
case ESlateTransformComponent::Rotation:
{
switch(SubComponent)
{
case ESlateTransformSubComponent::Pitch:
{
Value = ControlElement->Settings.LimitEnabled[3].GetForValueType(ValueType);
break;
}
case ESlateTransformSubComponent::Yaw:
{
Value = ControlElement->Settings.LimitEnabled[4].GetForValueType(ValueType);
break;
}
case ESlateTransformSubComponent::Roll:
{
Value = ControlElement->Settings.LimitEnabled[5].GetForValueType(ValueType);
break;
}
default:
{
break;
}
}
break;
}
case ESlateTransformComponent::Scale:
{
switch(SubComponent)
{
case ESlateTransformSubComponent::X:
{
Value = ControlElement->Settings.LimitEnabled[6].GetForValueType(ValueType);
break;
}
case ESlateTransformSubComponent::Y:
{
Value = ControlElement->Settings.LimitEnabled[7].GetForValueType(ValueType);
break;
}
case ESlateTransformSubComponent::Z:
{
Value = ControlElement->Settings.LimitEnabled[8].GetForValueType(ValueType);
break;
}
default:
{
break;
}
}
break;
}
}
}
break;
}
}
if(Value.IsSet())
{
if(FirstValue.IsSet())
{
if(FirstValue.GetValue() != Value.GetValue())
{
return ECheckBoxState::Undetermined;
}
}
else
{
FirstValue = Value;
}
}
}
}
if(!ensure(FirstValue.IsSet()))
{
return ECheckBoxState::Undetermined;
}
return FirstValue.GetValue() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
});
TransformWidgetArgs.OnToggleChanged_Lambda([ValueType, Keys, this, Hierarchy]
(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type RotationRepresentation,
ESlateTransformSubComponent::Type SubComponent,
ECheckBoxState CheckState
)
{
if(CheckState == ECheckBoxState::Undetermined)
{
return;
}
const bool Value = CheckState == ECheckBoxState::Checked;
FScopedTransaction Transaction(LOCTEXT("ChangeLimitToggle", "Change Limit Toggle"));
Hierarchy->Modify();
for(const FRigElementKey& Key : Keys)
{
if(FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
switch(ControlElement->Settings.ControlType)
{
case ERigControlType::Position:
case ERigControlType::Rotator:
case ERigControlType::Scale:
{
if(ControlElement->Settings.LimitEnabled.Num() == 3)
{
int32 Index = INDEX_NONE;
if (ControlElement->Settings.ControlType == ERigControlType::Rotator)
{
// TRotator is ordered Roll,Pitch,Yaw, while SNumericRotatorInputBox is ordered Pitch,Yaw,Roll
switch (SubComponent)
{
case ESlateTransformSubComponent::Pitch: Index = 1; break;
case ESlateTransformSubComponent::Yaw: Index = 2; break;
case ESlateTransformSubComponent::Roll: Index = 0; break;
}
}
else
{
Index = int32(SubComponent) - int32(ESlateTransformSubComponent::X);
}
if (Index != INDEX_NONE)
{
ControlElement->Settings.LimitEnabled[Index].SetForValueType(ValueType, Value);
}
}
break;
}
case ERigControlType::EulerTransform:
{
if(ControlElement->Settings.LimitEnabled.Num() == 9)
{
switch(Component)
{
case ESlateTransformComponent::Location:
{
switch(SubComponent)
{
case ESlateTransformSubComponent::X:
{
ControlElement->Settings.LimitEnabled[0].SetForValueType(ValueType, Value);
break;
}
case ESlateTransformSubComponent::Y:
{
ControlElement->Settings.LimitEnabled[1].SetForValueType(ValueType, Value);
break;
}
case ESlateTransformSubComponent::Z:
{
ControlElement->Settings.LimitEnabled[2].SetForValueType(ValueType, Value);
break;
}
default:
{
break;
}
}
break;
}
case ESlateTransformComponent::Rotation:
{
switch(SubComponent)
{
case ESlateTransformSubComponent::Pitch:
{
ControlElement->Settings.LimitEnabled[3].SetForValueType(ValueType, Value);
break;
}
case ESlateTransformSubComponent::Yaw:
{
ControlElement->Settings.LimitEnabled[4].SetForValueType(ValueType, Value);
break;
}
case ESlateTransformSubComponent::Roll:
{
ControlElement->Settings.LimitEnabled[5].SetForValueType(ValueType, Value);
break;
}
default:
{
break;
}
}
break;
}
case ESlateTransformComponent::Scale:
{
switch(SubComponent)
{
case ESlateTransformSubComponent::X:
{
ControlElement->Settings.LimitEnabled[6].SetForValueType(ValueType, Value);
break;
}
case ESlateTransformSubComponent::Y:
{
ControlElement->Settings.LimitEnabled[7].SetForValueType(ValueType, Value);
break;
}
case ESlateTransformSubComponent::Z:
{
ControlElement->Settings.LimitEnabled[8].SetForValueType(ValueType, Value);
break;
}
default:
{
break;
}
}
break;
}
}
}
break;
}
}
Hierarchy->SetControlSettings(ControlElement, ControlElement->Settings, true, true, true);
}
}
});
break;
}
default:
{
TransformWidgetArgs.AllowEditRotationRepresentation(true);
TransformWidgetArgs.DisplayRelativeWorld(bDisplayRelativeWorldOnCurrent);
TransformWidgetArgs.DisplayToggle(false);
TransformWidgetArgs._OnGetToggleChecked.Unbind();
TransformWidgetArgs._OnToggleChanged.Unbind();
break;
}
}
auto GetRelativeAbsoluteTransforms = [CurrentTransformType, Keys, Hierarchy](
const FRigElementKey& Key,
ERigTransformElementDetailsTransform::Type InTransformType = ERigTransformElementDetailsTransform::Max
) -> TPair<FEulerTransform, FEulerTransform>
{
if(InTransformType == ERigTransformElementDetailsTransform::Max)
{
InTransformType = CurrentTransformType;
}
FEulerTransform RelativeTransform = FEulerTransform::Identity;
FEulerTransform AbsoluteTransform = FEulerTransform::Identity;
const bool bInitial = InTransformType == ERigTransformElementDetailsTransform::Initial;
if(bInitial || InTransformType == ERigTransformElementDetailsTransform::Current)
{
RelativeTransform.FromFTransform(Hierarchy->GetLocalTransform(Key, bInitial));
AbsoluteTransform.FromFTransform(Hierarchy->GetGlobalTransform(Key, bInitial));
if(FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
switch(ControlElement->Settings.ControlType)
{
case ERigControlType::Rotator:
case ERigControlType::EulerTransform:
case ERigControlType::Transform:
case ERigControlType::TransformNoScale:
{
FVector Vector;
if(const UControlRig* ControlRig = Hierarchy->GetTypedOuter<UControlRig>())
{
Vector = ControlRig->GetControlSpecifiedEulerAngle(ControlElement, bInitial);
}
else
{
Vector = Hierarchy->GetControlSpecifiedEulerAngle(ControlElement, bInitial);
}
RelativeTransform.Rotation = FRotator(Vector.Y, Vector.Z, Vector.X);
break;
}
default:
{
break;
}
}
}
}
else
{
if(FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
const ERigControlType ControlType = ControlElement->Settings.ControlType;
if(InTransformType == ERigTransformElementDetailsTransform::Offset)
{
RelativeTransform.FromFTransform(Hierarchy->GetControlOffsetTransform(ControlElement, ERigTransformType::InitialLocal));
AbsoluteTransform.FromFTransform(Hierarchy->GetControlOffsetTransform(ControlElement, ERigTransformType::InitialGlobal));
}
else if(InTransformType == ERigTransformElementDetailsTransform::Minimum)
{
switch(ControlType)
{
case ERigControlType::Position:
{
const FVector Data =
(FVector)Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Minimum)
.Get<FVector3f>();
AbsoluteTransform = RelativeTransform = FEulerTransform(Data, FRotator::ZeroRotator, FVector::OneVector);
break;
}
case ERigControlType::Rotator:
{
const FVector Data =
(FVector)Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Minimum)
.Get<FVector3f>();
FRotator Rotator = FRotator::MakeFromEuler(Data);
AbsoluteTransform = RelativeTransform = FEulerTransform(FVector::ZeroVector, Rotator, FVector::OneVector);
break;
}
case ERigControlType::Scale:
{
const FVector Data =
(FVector)Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Minimum)
.Get<FVector3f>();
AbsoluteTransform = RelativeTransform = FEulerTransform(FVector::ZeroVector, FRotator::ZeroRotator, Data);
break;
}
case ERigControlType::EulerTransform:
{
const FRigControlValue::FEulerTransform_Float EulerTransform =
Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Minimum)
.Get<FRigControlValue::FEulerTransform_Float>();
AbsoluteTransform = RelativeTransform = EulerTransform.ToTransform();
break;
}
}
}
else if(InTransformType == ERigTransformElementDetailsTransform::Maximum)
{
switch(ControlType)
{
case ERigControlType::Position:
{
const FVector Data =
(FVector)Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Maximum)
.Get<FVector3f>();
AbsoluteTransform = RelativeTransform = FEulerTransform(Data, FRotator::ZeroRotator, FVector::OneVector);
break;
}
case ERigControlType::Rotator:
{
const FVector Data =
(FVector)Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Maximum)
.Get<FVector3f>();
FRotator Rotator = FRotator::MakeFromEuler(Data);
AbsoluteTransform = RelativeTransform = FEulerTransform(FVector::ZeroVector, Rotator, FVector::OneVector);
break;
}
case ERigControlType::Scale:
{
const FVector Data =
(FVector)Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Maximum)
.Get<FVector3f>();
AbsoluteTransform = RelativeTransform = FEulerTransform(FVector::ZeroVector, FRotator::ZeroRotator, Data);
break;
}
case ERigControlType::EulerTransform:
{
const FRigControlValue::FEulerTransform_Float EulerTransform =
Hierarchy->GetControlValue(ControlElement, ERigControlValueType::Maximum)
.Get<FRigControlValue::FEulerTransform_Float>();
AbsoluteTransform = RelativeTransform = EulerTransform.ToTransform();
break;
}
}
}
}
}
return TPair<FEulerTransform, FEulerTransform>(RelativeTransform, AbsoluteTransform);
};
auto GetCombinedTransform = [IsComponentRelative, GetRelativeAbsoluteTransforms](
const FRigElementKey& Key,
ERigTransformElementDetailsTransform::Type InTransformType = ERigTransformElementDetailsTransform::Max
) -> FEulerTransform
{
const TPair<FEulerTransform, FEulerTransform> TransformPair = GetRelativeAbsoluteTransforms(Key, InTransformType);
const FEulerTransform RelativeTransform = TransformPair.Key;
const FEulerTransform AbsoluteTransform = TransformPair.Value;
FEulerTransform Xfo;
Xfo.SetLocation((IsComponentRelative(0)) ? RelativeTransform.GetLocation() : AbsoluteTransform.GetLocation());
Xfo.SetRotator((IsComponentRelative(1)) ? RelativeTransform.Rotator() : AbsoluteTransform.Rotator());
Xfo.SetScale3D((IsComponentRelative(2)) ? RelativeTransform.GetScale3D() : AbsoluteTransform.GetScale3D());
return Xfo;
};
auto GetSingleTransform = [GetRelativeAbsoluteTransforms](
const FRigElementKey& Key,
bool bIsRelative,
ERigTransformElementDetailsTransform::Type InTransformType = ERigTransformElementDetailsTransform::Max
) -> FEulerTransform
{
const TPair<FEulerTransform, FEulerTransform> TransformPair = GetRelativeAbsoluteTransforms(Key, InTransformType);
const FEulerTransform RelativeTransform = TransformPair.Key;
const FEulerTransform AbsoluteTransform = TransformPair.Value;
return bIsRelative ? RelativeTransform : AbsoluteTransform;
};
const TWeakPtr<FRigTransformElementDetails> WeakThisPtr = StaticCastWeakPtr<FRigTransformElementDetails>(AsWeak());
auto SetSingleTransform = [CurrentTransformType, GetRelativeAbsoluteTransforms, WeakThisPtr, Hierarchy](
const FRigElementKey& Key,
FEulerTransform InTransform,
bool bIsRelative,
bool bSetupUndoRedo)
{
const TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin();
if(!StrongThisPtr)
{
return;
}
const bool bCurrent = CurrentTransformType == ERigTransformElementDetailsTransform::Current;
const bool bInitial = CurrentTransformType == ERigTransformElementDetailsTransform::Initial;
bool bConstructionModeEnabled = false;
if (UControlRig* DebuggedRig = Cast<UControlRig>(StrongThisPtr->PerElementInfos[0].GetBlueprint()->GetObjectBeingDebugged()))
{
bConstructionModeEnabled = DebuggedRig->IsConstructionModeEnabled();
}
TArray<URigHierarchy*> HierarchiesToUpdate;
HierarchiesToUpdate.Add(Hierarchy);
if(!bCurrent || bConstructionModeEnabled)
{
HierarchiesToUpdate.Add(StrongThisPtr->PerElementInfos[0].GetDefaultHierarchy());
}
for(URigHierarchy* HierarchyToUpdate : HierarchiesToUpdate)
{
if(bInitial || CurrentTransformType == ERigTransformElementDetailsTransform::Current)
{
if(bIsRelative)
{
HierarchyToUpdate->SetLocalTransform(Key, InTransform.ToFTransform(), bInitial, true, bSetupUndoRedo);
if(FRigControlElement* ControlElement = HierarchyToUpdate->Find<FRigControlElement>(Key))
{
switch(ControlElement->Settings.ControlType)
{
case ERigControlType::Rotator:
{
const FVector EulerAngle(InTransform.Rotator().Roll, InTransform.Rotator().Pitch, InTransform.Rotator().Yaw);
HierarchyToUpdate->SetControlSpecifiedEulerAngle(ControlElement, EulerAngle, bInitial);
const ERigControlValueType ValueType = bInitial ? ERigControlValueType::Initial : ERigControlValueType::Current;
const FRotator Rotator(Hierarchy->GetControlQuaternion(ControlElement, EulerAngle));
HierarchyToUpdate->SetControlValue(ControlElement, FRigControlValue::Make<FRotator>(Rotator), ValueType, bSetupUndoRedo);
break;
}
case ERigControlType::EulerTransform:
case ERigControlType::Transform:
case ERigControlType::TransformNoScale:
{
FVector EulerAngle(InTransform.Rotator().Roll, InTransform.Rotator().Pitch, InTransform.Rotator().Yaw);
HierarchyToUpdate->SetControlSpecifiedEulerAngle(ControlElement, EulerAngle, bInitial);
break;
}
default:
{
break;
}
}
}
}
else
{
HierarchyToUpdate->SetGlobalTransform(Key, InTransform.ToFTransform(), bInitial, true, bSetupUndoRedo);
}
}
else
{
if(FRigControlElement* ControlElement = HierarchyToUpdate->Find<FRigControlElement>(Key))
{
const ERigControlType ControlType = ControlElement->Settings.ControlType;
if(CurrentTransformType == ERigTransformElementDetailsTransform::Offset)
{
if(!bIsRelative)
{
const FTransform ParentTransform = HierarchyToUpdate->GetParentTransform(Key, bInitial);
InTransform.FromFTransform(InTransform.ToFTransform().GetRelativeTransform(ParentTransform));
}
HierarchyToUpdate->SetControlOffsetTransform(Key, InTransform.ToFTransform(), true, true, bSetupUndoRedo);
}
else if(CurrentTransformType == ERigTransformElementDetailsTransform::Minimum)
{
switch(ControlType)
{
case ERigControlType::Position:
{
const FRigControlValue Value = FRigControlValue::Make<FVector3f>((FVector3f)InTransform.GetLocation());
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Minimum, bSetupUndoRedo, true);
break;
}
case ERigControlType::Rotator:
{
const FVector3f Euler = (FVector3f)InTransform.Rotator().Euler();
const FRigControlValue Value = FRigControlValue::Make<FVector3f>(Euler);
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Minimum, bSetupUndoRedo, true);
break;
}
case ERigControlType::Scale:
{
const FRigControlValue Value = FRigControlValue::Make<FVector3f>((FVector3f)InTransform.GetScale3D());
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Minimum, bSetupUndoRedo, true);
break;
}
case ERigControlType::EulerTransform:
{
const FRigControlValue Value = FRigControlValue::Make<FRigControlValue::FEulerTransform_Float>(InTransform);
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Minimum, bSetupUndoRedo, true);
break;
}
}
}
else if(CurrentTransformType == ERigTransformElementDetailsTransform::Maximum)
{
switch(ControlType)
{
case ERigControlType::Position:
{
const FRigControlValue Value = FRigControlValue::Make<FVector3f>((FVector3f)InTransform.GetLocation());
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Maximum, bSetupUndoRedo, true);
break;
}
case ERigControlType::Rotator:
{
const FVector3f Euler = (FVector3f)InTransform.Rotator().Euler();
const FRigControlValue Value = FRigControlValue::Make<FVector3f>(Euler);
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Maximum, bSetupUndoRedo, true);
break;
}
case ERigControlType::Scale:
{
const FRigControlValue Value = FRigControlValue::Make<FVector3f>((FVector3f)InTransform.GetScale3D());
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Maximum, bSetupUndoRedo, true);
break;
}
case ERigControlType::EulerTransform:
{
const FRigControlValue Value = FRigControlValue::Make<FRigControlValue::FEulerTransform_Float>(InTransform);
HierarchyToUpdate->SetControlValue(ControlElement, Value, ERigControlValueType::Maximum, bSetupUndoRedo, true);
break;
}
}
}
}
}
}
};
TransformWidgetArgs.OnGetNumericValue_Lambda([Keys, GetCombinedTransform](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent) -> TOptional<FVector::FReal>
{
TOptional<FVector::FReal> FirstValue;
for(int32 Index = 0; Index < Keys.Num(); Index++)
{
const FRigElementKey& Key = Keys[Index];
FEulerTransform Xfo = GetCombinedTransform(Key);
TOptional<FVector::FReal> CurrentValue = SAdvancedTransformInputBox<FEulerTransform>::GetNumericValueFromTransform(Xfo, Component, Representation, SubComponent);
if(!CurrentValue.IsSet())
{
return CurrentValue;
}
if(Index == 0)
{
FirstValue = CurrentValue;
}
else
{
if(!FMath::IsNearlyEqual(FirstValue.GetValue(), CurrentValue.GetValue()))
{
return TOptional<FVector::FReal>();
}
}
}
return FirstValue;
});
TransformWidgetArgs.OnNumericValueChanged_Lambda(
[
Keys,
WeakThisPtr,
IsComponentRelative,
GetSingleTransform,
SetSingleTransform,
HierarchyToChange
](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal InNumericValue)
{
const TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin();
if (!StrongThisPtr)
{
return;
}
const bool bIsRelative = IsComponentRelative((int32)Component);
for(const FRigElementKey& Key : Keys)
{
FEulerTransform Transform = GetSingleTransform(Key, bIsRelative);
FEulerTransform PreviousTransform = Transform;
SAdvancedTransformInputBox<FEulerTransform>::ApplyNumericValueChange(Transform, InNumericValue, Component, Representation, SubComponent);
if(!FRigControlElementDetails::Equals(Transform, PreviousTransform))
{
if(!StrongThisPtr->SliderTransaction.IsValid())
{
StrongThisPtr->SliderTransaction = MakeShareable(new FScopedTransaction(NSLOCTEXT("ControlRigElementDetails", "ChangeNumericValue", "Change Numeric Value")));
HierarchyToChange->Modify();
}
SetSingleTransform(Key, Transform, bIsRelative, false);
}
}
});
TransformWidgetArgs.OnNumericValueCommitted_Lambda(
[
Keys,
WeakThisPtr,
IsComponentRelative,
GetSingleTransform,
SetSingleTransform,
HierarchyToChange
](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal InNumericValue,
ETextCommit::Type InCommitType)
{
const TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin();
if (!StrongThisPtr)
{
return;
}
const bool bIsRelative = IsComponentRelative((int32)Component);
{
FScopedTransaction Transaction(LOCTEXT("ChangeNumericValue", "Change Numeric Value"));
if(!StrongThisPtr->SliderTransaction.IsValid())
{
HierarchyToChange->Modify();
}
for(const FRigElementKey& Key : Keys)
{
FEulerTransform Transform = GetSingleTransform(Key, bIsRelative);
SAdvancedTransformInputBox<FEulerTransform>::ApplyNumericValueChange(Transform, InNumericValue, Component, Representation, SubComponent);
SetSingleTransform(Key, Transform, bIsRelative, true);
}
}
StrongThisPtr->SliderTransaction.Reset();
});
TransformWidgetArgs.OnBeginSliderMovement_Lambda(
[
WeakThisPtr
](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent)
{
TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin();
if (!StrongThisPtr)
{
return;
}
if (UControlRig* DebuggedRig = Cast<UControlRig>(StrongThisPtr->PerElementInfos[0].GetBlueprint()->GetObjectBeingDebugged()))
{
EControlRigInteractionType Type = EControlRigInteractionType::None;
switch (Component)
{
case ESlateTransformComponent::Location: Type = EControlRigInteractionType::Translate; break;
case ESlateTransformComponent::Rotation: Type = EControlRigInteractionType::Rotate; break;
case ESlateTransformComponent::Scale: Type = EControlRigInteractionType::Scale; break;
default: Type = EControlRigInteractionType::All;
}
DebuggedRig->InteractionType = (uint8)Type;
DebuggedRig->ElementsBeingInteracted.Reset();
for (const FPerElementInfo& ElementInfo : StrongThisPtr->PerElementInfos)
{
DebuggedRig->ElementsBeingInteracted.AddUnique(ElementInfo.Element.GetKey());
}
FControlRigInteractionScope* InteractionScope = new FControlRigInteractionScope(DebuggedRig);
StrongThisPtr->InteractionScopes.Add(InteractionScope);
}
});
TransformWidgetArgs.OnEndSliderMovement_Lambda(
[
WeakThisPtr
](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal InNumericValue)
{
const TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin();
if (!StrongThisPtr)
{
return;
}
if (UControlRig* DebuggedRig = Cast<UControlRig>(StrongThisPtr->PerElementInfos[0].GetBlueprint()->GetObjectBeingDebugged()))
{
DebuggedRig->InteractionType = (uint8)EControlRigInteractionType::None;
DebuggedRig->ElementsBeingInteracted.Reset();
}
for (FControlRigInteractionScope* InteractionScope : StrongThisPtr->InteractionScopes)
{
if (InteractionScope)
{
delete InteractionScope;
}
}
StrongThisPtr->InteractionScopes.Reset();
});
TransformWidgetArgs.OnCopyToClipboard_Lambda([Keys, IsComponentRelative, ConformComponentRelative, GetSingleTransform](
ESlateTransformComponent::Type InComponent
)
{
if(Keys.Num() == 0)
{
return;
}
// make sure that we use the same relative setting on all components when copying
ConformComponentRelative(0);
const bool bIsRelative = IsComponentRelative(0);
const FRigElementKey& FirstKey = Keys[0];
FEulerTransform Xfo = GetSingleTransform(FirstKey, bIsRelative);
FString Content;
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
const FVector Data = Xfo.GetLocation();
TBaseStructure<FVector>::Get()->ExportText(Content, &Data, &Data, nullptr, PPF_None, nullptr);
break;
}
case ESlateTransformComponent::Rotation:
{
const FRotator Data = Xfo.Rotator();
TBaseStructure<FRotator>::Get()->ExportText(Content, &Data, &Data, nullptr, PPF_None, nullptr);
break;
}
case ESlateTransformComponent::Scale:
{
const FVector Data = Xfo.GetScale3D();
TBaseStructure<FVector>::Get()->ExportText(Content, &Data, &Data, nullptr, PPF_None, nullptr);
break;
}
case ESlateTransformComponent::Max:
default:
{
TBaseStructure<FEulerTransform>::Get()->ExportText(Content, &Xfo, &Xfo, nullptr, PPF_None, nullptr);
break;
}
}
if(!Content.IsEmpty())
{
FPlatformApplicationMisc::ClipboardCopy(*Content);
}
});
TransformWidgetArgs.OnPasteFromClipboard_Lambda([this, Keys, IsComponentRelative, ConformComponentRelative, GetSingleTransform, SetSingleTransform, HierarchyToChange](
ESlateTransformComponent::Type InComponent
)
{
if(Keys.Num() == 0)
{
return;
}
// make sure that we use the same relative setting on all components when pasting
ConformComponentRelative(0);
const bool bIsRelative = IsComponentRelative(0);
FString Content;
FPlatformApplicationMisc::ClipboardPaste(Content);
if(Content.IsEmpty())
{
return;
}
FScopedTransaction Transaction(LOCTEXT("PasteTransform", "Paste Transform"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
FEulerTransform Xfo = GetSingleTransform(Key, bIsRelative);
{
class FRigPasteTransformWidgetErrorPipe : public FOutputDevice
{
public:
int32 NumErrors;
FRigPasteTransformWidgetErrorPipe()
: FOutputDevice()
, NumErrors(0)
{
}
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
{
UE_LOG(LogControlRig, Error, TEXT("Error Pasting to Widget: %s"), V);
NumErrors++;
}
};
FRigPasteTransformWidgetErrorPipe ErrorPipe;
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
FVector Data = Xfo.GetLocation();
TBaseStructure<FVector>::Get()->ImportText(*Content, &Data, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FVector>::Get()->GetName(), true);
Xfo.SetLocation(Data);
break;
}
case ESlateTransformComponent::Rotation:
{
FRotator Data = Xfo.Rotator();
TBaseStructure<FRotator>::Get()->ImportText(*Content, &Data, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FRotator>::Get()->GetName(), true);
Xfo.SetRotator(Data);
break;
}
case ESlateTransformComponent::Scale:
{
FVector Data = Xfo.GetScale3D();
TBaseStructure<FVector>::Get()->ImportText(*Content, &Data, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FVector>::Get()->GetName(), true);
Xfo.SetScale3D(Data);
break;
}
case ESlateTransformComponent::Max:
default:
{
TBaseStructure<FEulerTransform>::Get()->ImportText(*Content, &Xfo, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FEulerTransform>::Get()->GetName(), true);
break;
}
}
if(ErrorPipe.NumErrors == 0)
{
SetSingleTransform(Key, Xfo, bIsRelative, true);
}
}
}
});
TransformWidgetArgs.DiffersFromDefault_Lambda([
CurrentTransformType,
Keys,
GetSingleTransform
](
ESlateTransformComponent::Type InComponent) -> bool
{
for(const FRigElementKey& Key : Keys)
{
const FEulerTransform CurrentTransform = GetSingleTransform(Key, true);
FEulerTransform DefaultTransform;
switch(CurrentTransformType)
{
case ERigTransformElementDetailsTransform::Current:
{
DefaultTransform = GetSingleTransform(Key, true, ERigTransformElementDetailsTransform::Initial);
break;
}
default:
{
DefaultTransform = FEulerTransform::Identity;
break;
}
}
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
if(!DefaultTransform.GetLocation().Equals(CurrentTransform.GetLocation()))
{
return true;
}
break;
}
case ESlateTransformComponent::Rotation:
{
if(!DefaultTransform.Rotator().Equals(CurrentTransform.Rotator()))
{
return true;
}
break;
}
case ESlateTransformComponent::Scale:
{
if(!DefaultTransform.GetScale3D().Equals(CurrentTransform.GetScale3D()))
{
return true;
}
break;
}
default: // also no component whole transform
{
if(!DefaultTransform.GetLocation().Equals(CurrentTransform.GetLocation()) ||
!DefaultTransform.Rotator().Equals(CurrentTransform.Rotator()) ||
!DefaultTransform.GetScale3D().Equals(CurrentTransform.GetScale3D()))
{
return true;
}
break;
}
}
}
return false;
});
TransformWidgetArgs.OnResetToDefault_Lambda([this, CurrentTransformType, Keys, GetSingleTransform, SetSingleTransform, HierarchyToChange](
ESlateTransformComponent::Type InComponent)
{
FScopedTransaction Transaction(LOCTEXT("ResetTransformToDefault", "Reset Transform to Default"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
FEulerTransform CurrentTransform = GetSingleTransform(Key, true);
FEulerTransform DefaultTransform;
switch(CurrentTransformType)
{
case ERigTransformElementDetailsTransform::Current:
{
DefaultTransform = GetSingleTransform(Key, true, ERigTransformElementDetailsTransform::Initial);
break;
}
default:
{
DefaultTransform = FEulerTransform::Identity;
break;
}
}
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
CurrentTransform.SetLocation(DefaultTransform.GetLocation());
break;
}
case ESlateTransformComponent::Rotation:
{
CurrentTransform.SetRotator(DefaultTransform.Rotator());
break;
}
case ESlateTransformComponent::Scale:
{
CurrentTransform.SetScale3D(DefaultTransform.GetScale3D());
break;
}
default: // whole transform / max component
{
CurrentTransform = DefaultTransform;
break;
}
}
SetSingleTransform(Key, CurrentTransform, true, true);
}
});
return SAdvancedTransformInputBox<FEulerTransform>::ConstructGroupedTransformRows(
CategoryBuilder,
Label,
Tooltip,
TransformWidgetArgs,
NameContent);
}
ERigTransformElementDetailsTransform::Type FRigTransformElementDetails::GetTransformTypeFromValueType(
ERigControlValueType InValueType)
{
ERigTransformElementDetailsTransform::Type TransformType = ERigTransformElementDetailsTransform::Current;
switch(InValueType)
{
case ERigControlValueType::Initial:
{
TransformType = ERigTransformElementDetailsTransform::Initial;
break;
}
case ERigControlValueType::Minimum:
{
TransformType = ERigTransformElementDetailsTransform::Minimum;
break;
}
case ERigControlValueType::Maximum:
{
TransformType = ERigTransformElementDetailsTransform::Maximum;
break;
}
default:
{
break;
}
}
return TransformType;
}
void FRigBoneElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
FRigTransformElementDetails::CustomizeDetails(DetailBuilder);
CustomizeTransform(DetailBuilder);
CustomizeComponents(DetailBuilder);
CustomizeMetadata(DetailBuilder);
}
void FRigControlElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
FRigTransformElementDetails::CustomizeDetails(DetailBuilder);
CustomizeControl(DetailBuilder);
CustomizeValue(DetailBuilder);
CustomizeTransform(DetailBuilder);
CustomizeShape(DetailBuilder);
CustomizeAvailableSpaces(DetailBuilder);
CustomizeAnimationChannels(DetailBuilder);
CustomizeComponents(DetailBuilder);
CustomizeMetadata(DetailBuilder);
}
void FRigControlElementDetails::CustomizeValue(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
if(IsAnyElementNotOfType(ERigElementType::Control))
{
return;
}
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
// only show this section if all controls are the same type
const FRigControlElement* FirstControlElement = PerElementInfos[0].GetElement<FRigControlElement>();
const ERigControlType ControlType = FirstControlElement->Settings.ControlType;
bool bAllAnimationChannels = true;
for(const FPerElementInfo& Info : PerElementInfos)
{
const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>();
if(ControlElement->Settings.ControlType != ControlType)
{
return;
}
if(ControlElement->Settings.AnimationType != ERigControlAnimationType::AnimationChannel)
{
bAllAnimationChannels = false;
}
}
// transforms don't show their value here - instead they are shown in the transform section
if((ControlType == ERigControlType::EulerTransform ||
ControlType == ERigControlType::Transform ||
ControlType == ERigControlType::TransformNoScale) &&
!bAllAnimationChannels)
{
return;
}
TArray<FText> Labels = {
LOCTEXT("Initial", "Initial"),
LOCTEXT("Current", "Current")
};
TArray<FText> Tooltips = {
LOCTEXT("ValueInitialTooltip", "The initial animation value of the control"),
LOCTEXT("ValueCurrentTooltip", "The current animation value of the control")
};
TArray<ERigControlValueType> ValueTypes = {
ERigControlValueType::Initial,
ERigControlValueType::Current
};
// bool doesn't have limits,
// transform types already got filtered out earlier.
// integers with enums don't have limits either
if(ControlType != ERigControlType::Bool &&
(ControlType != ERigControlType::Integer || !FirstControlElement->Settings.ControlEnum))
{
Labels.Append({
LOCTEXT("Min", "Min"),
LOCTEXT("Max", "Max")
});
Tooltips.Append({
LOCTEXT("ValueMinimumTooltip", "The minimum limit(s) for the control"),
LOCTEXT("ValueMaximumTooltip", "The maximum limit(s) for the control")
});
ValueTypes.Append({
ERigControlValueType::Minimum,
ERigControlValueType::Maximum
});
}
IDetailCategoryBuilder& ValueCategory = DetailBuilder.EditCategory(TEXT("Value"), LOCTEXT("Value", "Value"));
if(!PickedValueTypes.IsValid())
{
PickedValueTypes = MakeShareable(new TArray<ERigControlValueType>({ERigControlValueType::Current}));
}
TSharedPtr<SSegmentedControl<ERigControlValueType>> ValueTypeChoiceWidget =
SSegmentedControl<ERigControlValueType>::Create(
ValueTypes,
Labels,
Tooltips,
*PickedValueTypes.Get(),
true,
SSegmentedControl<ERigControlValueType>::FOnValuesChanged::CreateLambda(
[](TArray<ERigControlValueType> NewSelection)
{
(*FRigControlElementDetails::PickedValueTypes.Get()) = NewSelection;
}
)
);
AddChoiceWidgetRow(ValueCategory, FText::FromString(TEXT("ValueType")), ValueTypeChoiceWidget.ToSharedRef());
TArray<FRigElementKey> Keys = GetElementKeys();
Keys = Hierarchy->SortKeys(Keys);
for(int32 Index=0; Index < ValueTypes.Num(); Index++)
{
const ERigControlValueType ValueType = ValueTypes[Index];
const TAttribute<EVisibility> VisibilityAttribute =
TAttribute<EVisibility>::CreateLambda([ValueType, ValueTypeChoiceWidget]()-> EVisibility
{
return ValueTypeChoiceWidget->HasValue(ValueType) ? EVisibility::Visible : EVisibility::Collapsed;
});
switch(ControlType)
{
case ERigControlType::Bool:
{
CreateBoolValueWidgetRow(Keys, ValueCategory, Labels[Index], Tooltips[Index], ValueType, VisibilityAttribute);
break;
}
case ERigControlType::Float:
case ERigControlType::ScaleFloat:
{
CreateFloatValueWidgetRow(Keys, ValueCategory, Labels[Index], Tooltips[Index], ValueType, VisibilityAttribute);
break;
}
case ERigControlType::Integer:
{
bool bIsEnum = false;
for(const FRigElementKey& Key : Keys)
{
if(const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
if(ControlElement->Settings.ControlEnum)
{
bIsEnum = true;
break;
}
}
}
if(bIsEnum)
{
CreateEnumValueWidgetRow(Keys, ValueCategory, Labels[Index], Tooltips[Index], ValueType, VisibilityAttribute);
}
else
{
CreateIntegerValueWidgetRow(Keys, ValueCategory, Labels[Index], Tooltips[Index], ValueType, VisibilityAttribute);
}
break;
}
case ERigControlType::Vector2D:
{
CreateVector2DValueWidgetRow(Keys, ValueCategory, Labels[Index], Tooltips[Index], ValueType, VisibilityAttribute);
break;
}
case ERigControlType::Position:
case ERigControlType::Rotator:
case ERigControlType::Scale:
{
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs = SAdvancedTransformInputBox<FEulerTransform>::FArguments()
.DisplayToggle(false)
.DisplayRelativeWorld(true)
.Visibility(VisibilityAttribute)
.PreventThrottling(true);
CreateTransformComponentValueWidgetRow(
ControlType,
GetElementKeys(),
TransformWidgetArgs,
ValueCategory,
Labels[Index],
Tooltips[Index],
GetTransformTypeFromValueType(ValueType),
ValueType);
break;
}
default:
{
break;
}
}
}
}
void FRigControlElementDetails::CustomizeControl(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
if(IsAnyElementNotOfType(ERigElementType::Control))
{
return;
}
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural;
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
const TSharedPtr<IPropertyHandle> SettingsHandle = DetailBuilder.GetProperty(TEXT("Settings"));
DetailBuilder.HideProperty(SettingsHandle);
IDetailCategoryBuilder& ControlCategory = DetailBuilder.EditCategory(TEXT("Control"), LOCTEXT("Control", "Control"));
const bool bAllAnimationChannels = !IsAnyControlNotOfAnimationType(ERigControlAnimationType::AnimationChannel);
static const FText DisplayNameText = LOCTEXT("DisplayName", "Display Name");
static const FText ChannelNameText = LOCTEXT("ChannelName", "Channel Name");
const FText DisplayNameLabelText = bAllAnimationChannels ? ChannelNameText : DisplayNameText;
const TSharedPtr<IPropertyHandle> DisplayNameHandle = SettingsHandle->GetChildHandle(TEXT("DisplayName"));
ControlCategory.AddCustomRow(DisplayNameLabelText)
.IsEnabled(bIsEnabled)
.NameContent()
[
DisplayNameHandle->CreatePropertyNameWidget(DisplayNameLabelText)
]
.ValueContent()
[
SNew(SInlineEditableTextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FRigControlElementDetails::GetDisplayName)
.OnTextCommitted(this, &FRigControlElementDetails::SetDisplayName)
.OnVerifyTextChanged_Lambda([this](const FText& InText, FText& OutErrorMessage) -> bool
{
return OnVerifyDisplayNameChanged(InText, OutErrorMessage, GetElementKey());
})
.IsEnabled(bIsEnabled && (PerElementInfos.Num() == 1))
];
if(bAllAnimationChannels)
{
ControlCategory.AddCustomRow(FText::FromString(TEXT("Script Name")))
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Script Name")))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(!bIsProcedural)
]
.ValueContent()
[
SNew(SInlineEditableTextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FRigBaseElementDetails::GetName)
.OnTextCommitted(this, &FRigBaseElementDetails::SetName)
.OnVerifyTextChanged(this, &FRigBaseElementDetails::OnVerifyNameChanged)
.IsEnabled(!bIsProcedural && PerElementInfos.Num() == 1)
];
}
const TSharedRef<IPropertyUtilities> PropertyUtilities = DetailBuilder.GetPropertyUtilities();
// when control type changes, we have to refresh detail panel
const TSharedPtr<IPropertyHandle> AnimationTypeHandle = SettingsHandle->GetChildHandle(TEXT("AnimationType"));
AnimationTypeHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda(
[this, PropertyUtilities, HierarchyToChange, Hierarchy]()
{
TArray<FRigControlElement> ControlElementsInView = GetElementsInDetailsView<FRigControlElement>();
if (HierarchyToChange && ControlElementsInView.Num() == PerElementInfos.Num())
{
HierarchyToChange->Modify();
for(int32 ControlIndex = 0; ControlIndex< ControlElementsInView.Num(); ControlIndex++)
{
const FRigControlElement& ViewElement = ControlElementsInView[ControlIndex];
FRigControlElement* ControlElement = PerElementInfos[ControlIndex].GetDefaultElement<FRigControlElement>();
ControlElement->Settings.AnimationType = ViewElement.Settings.AnimationType;
ControlElement->Settings.bGroupWithParentControl =
ControlElement->Settings.ControlType == ERigControlType::Bool ||
ControlElement->Settings.ControlType == ERigControlType::Float ||
ControlElement->Settings.ControlType == ERigControlType::ScaleFloat ||
ControlElement->Settings.ControlType == ERigControlType::Integer ||
ControlElement->Settings.ControlType == ERigControlType::Vector2D;
switch(ControlElement->Settings.AnimationType)
{
case ERigControlAnimationType::AnimationControl:
{
ControlElement->Settings.ShapeVisibility = ERigControlVisibility::UserDefined;
ControlElement->Settings.bShapeVisible = true;
break;
}
case ERigControlAnimationType::AnimationChannel:
{
ControlElement->Settings.ShapeVisibility = ERigControlVisibility::UserDefined;
ControlElement->Settings.bShapeVisible = false;
break;
}
case ERigControlAnimationType::ProxyControl:
{
ControlElement->Settings.ShapeVisibility = ERigControlVisibility::BasedOnSelection;
ControlElement->Settings.bShapeVisible = true;
ControlElement->Settings.bGroupWithParentControl = false;
break;
}
default:
{
ControlElement->Settings.ShapeVisibility = ERigControlVisibility::UserDefined;
ControlElement->Settings.bShapeVisible = true;
ControlElement->Settings.bGroupWithParentControl = false;
break;
}
}
HierarchyToChange->SetControlSettings(ControlElement, ControlElement->Settings, true, true, true);
PerElementInfos[ControlIndex].WrapperObject->SetContent<FRigControlElement>(*ControlElement);
if (HierarchyToChange != Hierarchy)
{
if(FRigControlElement* OtherControlElement = PerElementInfos[0].GetElement<FRigControlElement>())
{
OtherControlElement->Settings = ControlElement->Settings;
Hierarchy->SetControlSettings(OtherControlElement, OtherControlElement->Settings, true, true, true);
}
}
}
PropertyUtilities->ForceRefresh();
}
}
));
ControlCategory.AddProperty(AnimationTypeHandle.ToSharedRef())
.IsEnabled(bIsEnabled);
// when control type changes, we have to refresh detail panel
const TSharedPtr<IPropertyHandle> ControlTypeHandle = SettingsHandle->GetChildHandle(TEXT("ControlType"));
ControlTypeHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda(
[this, PropertyUtilities]()
{
TArray<FRigControlElement> ControlElementsInView = GetElementsInDetailsView<FRigControlElement>();
HandleControlTypeChanged(ControlElementsInView[0].Settings.ControlType, TArray<FRigElementKey>(), PropertyUtilities);
}
));
ControlCategory.AddProperty(ControlTypeHandle.ToSharedRef())
.IsEnabled(bIsEnabled);
const bool bSupportsShape = !IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationChannel) &&
!IsAnyControlOfAnimationType(ERigControlAnimationType::VisualCue);
if (HierarchyToChange != nullptr)
{
bool bEnableGroupWithParentControl = true;
for(const FPerElementInfo& Info : PerElementInfos)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
bool bSingleEnableGroupWithParentControl = false;
if(const FRigControlElement* ParentElement =
Cast<FRigControlElement>(Info.GetHierarchy()->GetFirstParent(ControlElement)))
{
if(ControlElement->Settings.IsAnimatable() &&
Info.GetHierarchy()->GetChildren(ControlElement).IsEmpty())
{
bSingleEnableGroupWithParentControl = true;
}
}
if(!bSingleEnableGroupWithParentControl)
{
bEnableGroupWithParentControl = false;
break;
}
}
}
if(bEnableGroupWithParentControl)
{
const TSharedPtr<IPropertyHandle> GroupWithParentControlHandle = SettingsHandle->GetChildHandle(TEXT("bGroupWithParentControl"));
ControlCategory.AddProperty(GroupWithParentControlHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Group Channels")))
.IsEnabled(bIsEnabled);
}
}
if(bSupportsShape &&
!(IsAnyControlNotOfValueType(ERigControlType::Integer) &&
IsAnyControlNotOfValueType(ERigControlType::Float) &&
IsAnyControlNotOfValueType(ERigControlType::ScaleFloat) &&
IsAnyControlNotOfValueType(ERigControlType::Vector2D)))
{
const TSharedPtr<IPropertyHandle> PrimaryAxisHandle = SettingsHandle->GetChildHandle(TEXT("PrimaryAxis"));
ControlCategory.AddProperty(PrimaryAxisHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Primary Axis")))
.IsEnabled(bIsEnabled);
}
if(CVarControlRigHierarchyEnableRotationOrder.GetValueOnAnyThread())
{
if (IsAnyControlOfValueType(ERigControlType::EulerTransform) || IsAnyControlOfValueType(ERigControlType::Rotator))
{
const TSharedPtr<IPropertyHandle> UsePreferredRotationOrderHandle = SettingsHandle->GetChildHandle(TEXT("bUsePreferredRotationOrder"));
ControlCategory.AddProperty(UsePreferredRotationOrderHandle.ToSharedRef())
.DisplayName(FText::FromString(TEXT("Use Preferred Rotation Order")))
.IsEnabled(bIsEnabled);
const TSharedPtr<IPropertyHandle> PreferredRotationOrderHandle = SettingsHandle->GetChildHandle(TEXT("PreferredRotationOrder"));
ControlCategory.AddProperty(PreferredRotationOrderHandle.ToSharedRef())
.DisplayName(FText::FromString(TEXT("Preferred Rotation Order")))
.IsEnabled(bIsEnabled);
}
}
if(IsAnyControlOfValueType(ERigControlType::Integer))
{
FDetailWidgetRow* EnumWidgetRow = &ControlCategory.AddCustomRow(FText::FromString(TEXT("ControlEnum")))
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Control Enum")))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(bIsEnabled)
]
.ValueContent()
[
SNew(SRigVMEnumPicker)
.OnEnumChanged(this, &FRigControlElementDetails::HandleControlEnumChanged, PropertyUtilities)
.IsEnabled(bIsEnabled)
.GetCurrentEnum_Lambda([this]()
{
UEnum* CommonControlEnum = nullptr;
for (int32 ControlIndex=0; ControlIndex < PerElementInfos.Num(); ++ControlIndex)
{
FPerElementInfo& Info = PerElementInfos[ControlIndex];
const FRigControlElement ControlInView = Info.WrapperObject->GetContent<FRigControlElement>();
FRigControlElement* ControlBeingCustomized = Info.GetDefaultElement<FRigControlElement>();
UEnum* ControlEnum = ControlBeingCustomized->Settings.ControlEnum;
if (ControlIndex == 0)
{
CommonControlEnum = ControlEnum;
}
else if(ControlEnum != CommonControlEnum)
{
CommonControlEnum = nullptr;
break;
}
}
return CommonControlEnum;
})
];
}
if(bSupportsShape)
{
const TSharedPtr<IPropertyHandle> RestrictSpaceSwitchingHandle = SettingsHandle->GetChildHandle(TEXT("bRestrictSpaceSwitching"));
ControlCategory
.AddProperty(RestrictSpaceSwitchingHandle.ToSharedRef())
.DisplayName(FText::FromString(TEXT("Restrict Switching")))
.IsEnabled(bIsEnabled);
// Available Spaces is now handled by its own category (CustomizeAvailableSpaces)
//const TSharedPtr<IPropertyHandle> CustomizationHandle = SettingsHandle->GetChildHandle(TEXT("Customization"));
//const TSharedPtr<IPropertyHandle> AvailableSpacesHandle = CustomizationHandle->GetChildHandle(TEXT("AvailableSpaces"));
//ControlCategory.AddProperty(AvailableSpacesHandle.ToSharedRef())
//.IsEnabled(bIsEnabled);
}
TArray<FRigElementKey> Keys = GetElementKeys();
if(bSupportsShape)
{
const TSharedPtr<IPropertyHandle> DrawLimitsHandle = SettingsHandle->GetChildHandle(TEXT("bDrawLimits"));
ControlCategory
.AddProperty(DrawLimitsHandle.ToSharedRef()).DisplayName(FText::FromString(TEXT("Draw Limits")))
.IsEnabled(TAttribute<bool>::CreateLambda([Keys, Hierarchy, bIsEnabled]() -> bool
{
if(!bIsEnabled)
{
return false;
}
for(const FRigElementKey& Key : Keys)
{
if(const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
if(ControlElement->Settings.LimitEnabled.Contains(FRigControlLimitEnabled(true, true)))
{
return true;
}
}
}
return false;
}));
}
ERigControlType CommonControlType = ERigControlType::Bool;
if(GetCommonControlType(CommonControlType))
{
if(FRigControlTransformChannelDetails::GetVisibleChannelsForControlType(CommonControlType) != nullptr)
{
const TSharedPtr<IPropertyHandle> FilteredChannelsHandle = SettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRigControlSettings, FilteredChannels));
ControlCategory.AddProperty(FilteredChannelsHandle.ToSharedRef())
.IsEnabled(bIsEnabled);
}
}
if(IsAnyControlOfAnimationType(ERigControlAnimationType::ProxyControl) || IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationControl))
{
ControlCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("DrivenControls")).ToSharedRef())
.IsEnabled(bIsEnabled);
}
}
void FRigControlElementDetails::HandleControlEnumChanged(TSharedPtr<FString> InItem, ESelectInfo::Type InSelectionInfo, const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
PropertyUtilities->ForceRefresh();
UEnum* ControlEnum = FindObject<UEnum>(nullptr, **InItem.Get(), false);
for(int32 ControlIndex = 0; ControlIndex < PerElementInfos.Num(); ControlIndex++)
{
FPerElementInfo& Info = PerElementInfos[ControlIndex];
const FRigControlElement ControlInView = Info.WrapperObject->GetContent<FRigControlElement>();
FRigControlElement* ControlBeingCustomized = Info.GetDefaultElement<FRigControlElement>();
ControlBeingCustomized->Settings.ControlEnum = ControlEnum;
if (ControlEnum != nullptr)
{
int32 Maximum = (int32)ControlEnum->GetMaxEnumValue() - 1;
ControlBeingCustomized->Settings.MinimumValue.Set<int32>(0);
ControlBeingCustomized->Settings.MaximumValue.Set<int32>(Maximum);
ControlBeingCustomized->Settings.LimitEnabled.Reset();
ControlBeingCustomized->Settings.LimitEnabled.Add(true);
Info.GetDefaultHierarchy()->SetControlSettings(ControlBeingCustomized, ControlBeingCustomized->Settings, true, true, true);
FRigControlValue InitialValue = Info.GetDefaultHierarchy()->GetControlValue(ControlBeingCustomized, ERigControlValueType::Initial);
FRigControlValue CurrentValue = Info.GetDefaultHierarchy()->GetControlValue(ControlBeingCustomized, ERigControlValueType::Current);
ControlBeingCustomized->Settings.ApplyLimits(InitialValue);
ControlBeingCustomized->Settings.ApplyLimits(CurrentValue);
Info.GetDefaultHierarchy()->SetControlValue(ControlBeingCustomized, InitialValue, ERigControlValueType::Initial, false, false, true);
Info.GetDefaultHierarchy()->SetControlValue(ControlBeingCustomized, CurrentValue, ERigControlValueType::Current, false, false, true);
if (UControlRig* DebuggedRig = Cast<UControlRig>(Info.GetBlueprint()->GetObjectBeingDebugged()))
{
URigHierarchy* DebuggedHierarchy = DebuggedRig->GetHierarchy();
if(FRigControlElement* DebuggedControlElement = DebuggedHierarchy->Find<FRigControlElement>(ControlBeingCustomized->GetKey()))
{
DebuggedControlElement->Settings.MinimumValue.Set<int32>(0);
DebuggedControlElement->Settings.MaximumValue.Set<int32>(Maximum);
DebuggedHierarchy->SetControlSettings(DebuggedControlElement, DebuggedControlElement->Settings, true, true, true);
DebuggedHierarchy->SetControlValue(DebuggedControlElement, InitialValue, ERigControlValueType::Initial);
DebuggedHierarchy->SetControlValue(DebuggedControlElement, CurrentValue, ERigControlValueType::Current);
}
}
}
Info.WrapperObject->SetContent<FRigControlElement>(*ControlBeingCustomized);
}
}
void FRigControlElementDetails::CustomizeAnimationChannels(IDetailLayoutBuilder& DetailBuilder)
{
// We only show this section for parents of animation channels
if(!IsAnyControlNotOfAnimationType(ERigControlAnimationType::AnimationChannel))
{
// If all controls are animation channels, just return
return;
}
// only show this if only one control is selected
if(PerElementInfos.Num() != 1)
{
return;
}
const FRigControlElement* ControlElement = PerElementInfos[0].GetElement<FRigControlElement>();
if(ControlElement == nullptr)
{
return;
}
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural;
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("AnimationChannels"), LOCTEXT("AnimationChannels", "Animation Channels"));
const TSharedRef<IPropertyUtilities> PropertyUtilities = DetailBuilder.GetPropertyUtilities();
const TSharedRef<SHorizontalBox> HeaderContentWidget = SNew(SHorizontalBox);
HeaderContentWidget->AddSlot()
.HAlign(HAlign_Right)
[
SNew(SButton)
.IsEnabled(bIsEnabled)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ContentPadding(FMargin(1, 0))
.OnClicked(this, &FRigControlElementDetails::OnAddAnimationChannelClicked)
.HAlign(HAlign_Right)
.ToolTipText(LOCTEXT("AddAnimationChannelToolTip", "Add a new animation channel"))
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
Category.HeaderContent(HeaderContentWidget);
const TArray<FRigControlElement*> AnimationChannels = Hierarchy->GetAnimationChannels(ControlElement, false);
const bool bHasAnimationChannels = !AnimationChannels.IsEmpty();
const FRigElementKey ControlElementKey = ControlElement->GetKey();
for(const FRigControlElement* AssignedAnimationChannel : AnimationChannels)
{
const FRigElementKey ChildElementKey = AssignedAnimationChannel->GetKey();
const bool bIsDirectlyParentedAnimationChannel = HierarchyToChange->GetDefaultParent(ChildElementKey) == ControlElementKey;
const TPair<const FSlateBrush*, FSlateColor> BrushAndColor = SRigHierarchyItem::GetBrushForElementType(Hierarchy, ChildElementKey);
static TArray<TSharedPtr<ERigControlType>> ControlValueTypes;
if(ControlValueTypes.IsEmpty())
{
const UEnum* ValueTypeEnum = StaticEnum<ERigControlType>();
for(int64 EnumValue = 0; EnumValue < ValueTypeEnum->GetMaxEnumValue(); EnumValue++)
{
if(ValueTypeEnum->HasMetaData(TEXT("Hidden"), (int32)EnumValue))
{
continue;
}
ControlValueTypes.Add(MakeShareable(new ERigControlType((ERigControlType)EnumValue)));
}
}
TSharedPtr<SButton> SelectAnimationChannelButton;
TSharedPtr<SImage> SelectAnimationChannelImage;
TSharedPtr<SWidget> NameContent;
SAssignNew(NameContent, SHorizontalBox)
.IsEnabled(bIsEnabled)
+ SHorizontalBox::Slot()
.MaxWidth(32)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 3.f, 0.f))
[
SNew(SComboButton)
.ContentPadding(0)
.HasDownArrow(false)
.ButtonContent()
[
SNew(SImage)
.Image(BrushAndColor.Key)
.ColorAndOpacity(BrushAndColor.Value)
]
.MenuContent()
[
SNew(SListView<TSharedPtr<ERigControlType>>)
.ListItemsSource( &ControlValueTypes )
.OnGenerateRow(this, &FRigControlElementDetails::HandleGenerateAnimationChannelTypeRow, ChildElementKey)
.OnSelectionChanged(this, &FRigControlElementDetails::HandleControlTypeChanged, ChildElementKey, PropertyUtilities)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 8.f, 0.f))
[
SNew(SInlineEditableTextBlock)
.Font(bIsDirectlyParentedAnimationChannel ? IDetailLayoutBuilder::GetDetailFont() : IDetailLayoutBuilder::GetDetailFontItalic())
.Text_Lambda([this, ChildElementKey]() -> FText
{
return GetDisplayNameForElement(ChildElementKey);
})
.OnTextCommitted_Lambda([this, ChildElementKey](const FText& InNewText, ETextCommit::Type InCommitType)
{
SetDisplayNameForElement(InNewText, InCommitType, ChildElementKey);
})
.OnVerifyTextChanged_Lambda([this, ChildElementKey](const FText& InText, FText& OutErrorMessage) -> bool
{
return OnVerifyDisplayNameChanged(InText, OutErrorMessage, ChildElementKey);
})
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 0.f, 0.f))
[
SAssignNew(SelectAnimationChannelButton, SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.OnClicked_Lambda([this, ChildElementKey]() -> FReply
{
return OnSelectElementClicked(ChildElementKey);
})
.ContentPadding(0)
.ToolTipText(NSLOCTEXT("ControlRigElementDetails", "SelectAnimationChannelInHierarchyToolTip", "Select Animation Channel"))
[
SAssignNew(SelectAnimationChannelImage, SImage)
.Image(FAppStyle::GetBrush("Icons.Search"))
]
];
SelectAnimationChannelImage->SetColorAndOpacity(TAttribute<FSlateColor>::CreateLambda([this, SelectAnimationChannelButton]() { return FRigElementKeyDetails::OnGetWidgetForeground(SelectAnimationChannelButton); }));
const FText Label = FText::FromString(FString::Printf(TEXT("Channel%s"), *AssignedAnimationChannel->GetDisplayName().ToString()));
const TArray<FRigElementKey> ChildElementKeys = {ChildElementKey};
TAttribute<EVisibility> Visibility = EVisibility::Visible;
FDetailWidgetRow* WidgetRow = nullptr;
switch(AssignedAnimationChannel->Settings.ControlType)
{
case ERigControlType::Bool:
{
WidgetRow = &CreateBoolValueWidgetRow(ChildElementKeys, Category, Label, FText(), ERigControlValueType::Current, Visibility, NameContent);
break;
}
case ERigControlType::Float:
case ERigControlType::ScaleFloat:
{
WidgetRow = &CreateFloatValueWidgetRow(ChildElementKeys, Category, Label, FText(), ERigControlValueType::Current, Visibility, NameContent);
break;
}
case ERigControlType::Integer:
{
if(AssignedAnimationChannel->Settings.ControlEnum)
{
WidgetRow = &CreateEnumValueWidgetRow(ChildElementKeys, Category, Label, FText(), ERigControlValueType::Current, Visibility, NameContent);
}
else
{
WidgetRow = &CreateIntegerValueWidgetRow(ChildElementKeys, Category, Label, FText(), ERigControlValueType::Current, Visibility, NameContent);
}
break;
}
case ERigControlType::Vector2D:
{
WidgetRow = &CreateVector2DValueWidgetRow(ChildElementKeys, Category, Label, FText(), ERigControlValueType::Current, Visibility, NameContent);
break;
}
case ERigControlType::Position:
case ERigControlType::Rotator:
case ERigControlType::Scale:
{
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs =
SAdvancedTransformInputBox<FEulerTransform>::FArguments()
.DisplayToggle(false)
.DisplayRelativeWorld(false)
.Visibility(EVisibility::Visible)
.PreventThrottling(true);
WidgetRow = &CreateTransformComponentValueWidgetRow(
AssignedAnimationChannel->Settings.ControlType,
ChildElementKeys,
TransformWidgetArgs,
Category,
Label,
FText(),
GetTransformTypeFromValueType(ERigControlValueType::Current),
ERigControlValueType::Current,
NameContent);
break;
}
case ERigControlType::Transform:
case ERigControlType::EulerTransform:
{
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs =
SAdvancedTransformInputBox<FEulerTransform>::FArguments()
.DisplayToggle(false)
.DisplayRelativeWorld(false)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Visibility(EVisibility::Visible)
.PreventThrottling(true);
WidgetRow = &CreateEulerTransformValueWidgetRow(
ChildElementKeys,
TransformWidgetArgs,
Category,
Label,
FText(),
ERigTransformElementDetailsTransform::Current,
ERigControlValueType::Current,
NameContent);
break;
}
default:
{
WidgetRow = &Category.AddCustomRow(Label)
.NameContent()
[
NameContent.ToSharedRef()
];
break;
}
}
if(WidgetRow)
{
if(bIsDirectlyParentedAnimationChannel)
{
WidgetRow->AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ChildElementKeys, HierarchyToChange]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("DeleteAnimationChannels", "Delete Animation Channels"));
HierarchyToChange->Modify();
for(const FRigElementKey& KeyToRemove : ChildElementKeys)
{
Controller->RemoveElement(KeyToRemove, true, true);
}
}
})),
LOCTEXT("DeleteAnimationChannel", "Delete"),
LOCTEXT("DeleteAnimationChannelTooltip", "Deletes this animation channel"),
FSlateIcon());
}
else
{
WidgetRow->AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ControlElementKey, ChildElementKeys, HierarchyToChange, PropertyUtilities]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("RemoveAnimationChannelHosts", "Remove Animation Channel Hosts"));
HierarchyToChange->Modify();
for(const FRigElementKey& KeyToRemove : ChildElementKeys)
{
Controller->RemoveChannelHost(KeyToRemove, ControlElementKey, true, true);
}
PropertyUtilities->ForceRefresh();
}
})),
LOCTEXT("RemoveAnimationChannelHost", "Remove from this host"),
LOCTEXT("RemoveAnimationChannelHostTooltip", "Remove the animation channel from this host"),
FSlateIcon());
}
// move up or down
WidgetRow->AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ControlElementKey, ChildElementKeys, HierarchyToChange]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("MoveAnimationChannelUpTransaction", "Move Animation Channel Up"));
HierarchyToChange->Modify();
for(const FRigElementKey& KeyToMove : ChildElementKeys)
{
const int32 LocalIndex = HierarchyToChange->GetLocalIndex(KeyToMove);
Controller->ReorderElement(KeyToMove, LocalIndex - 1, true);
}
Controller->SelectElement(ControlElementKey, true, true);
}
})),
LOCTEXT("MoveAnimationChannelUp", "Move Up"),
LOCTEXT("MoveAnimationChannelUpTooltip", "Reorders this animation channel to show up one higher"),
FSlateIcon());
WidgetRow->AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ControlElementKey, ChildElementKeys, HierarchyToChange]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("MoveAnimationChannelDownTransaction", "Move Animation Channel Down"));
HierarchyToChange->Modify();
for(const FRigElementKey& KeyToMove : ChildElementKeys)
{
const int32 LocalIndex = HierarchyToChange->GetLocalIndex(KeyToMove);
Controller->ReorderElement(KeyToMove, LocalIndex + 1, true);
}
Controller->SelectElement(ControlElementKey, true, true);
}
})),
LOCTEXT("MoveAnimationChannelDown", "Move Down"),
LOCTEXT("MoveAnimationChannelDownTooltip", "Reorders this animation channel to show up one lower"),
FSlateIcon());
}
}
Category.InitiallyCollapsed(!bHasAnimationChannels);
if(!bHasAnimationChannels)
{
Category.AddCustomRow(FText()).WholeRowContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 0.f, 0.f))
[
SNew(STextBlock)
.IsEnabled(bIsEnabled)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LOCTEXT("NoAnimationChannels", "No animation channels"))
]
];
}
}
void FRigControlElementDetails::CustomizeAvailableSpaces(IDetailLayoutBuilder& DetailBuilder)
{
// only show this if only one control / animation channel is selected
if(PerElementInfos.Num() != 1)
{
return;
}
const FRigControlElement* ControlElement = PerElementInfos[0].GetElement<FRigControlElement>();
if(ControlElement == nullptr)
{
return;
}
const bool bIsAnimationChannel = IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationChannel);
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural;
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
static const FText ControlSpaces = LOCTEXT("AvailableSpaces", "Available Spaces");
static const FText ChannelHosts = LOCTEXT("ChannelHosts", "Channel Hosts");
static const FText ControlSpacesToolTip = LOCTEXT("AvailableSpacesToolTip", "Spaces available for this Control");
static const FText ChannelHostsToolTip = LOCTEXT("ChannelHostsToolTip", "A list of controls this channel is listed under");
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("MultiParents"), bIsAnimationChannel ? ChannelHosts : ControlSpaces);
Category.SetToolTip(bIsAnimationChannel ? ChannelHostsToolTip : ControlSpaces);
const TSharedRef<IPropertyUtilities> PropertyUtilities = DetailBuilder.GetPropertyUtilities();
DisplaySettings.bShowBones = true;
DisplaySettings.bShowControls = true;
DisplaySettings.bShowNulls = true;
DisplaySettings.bShowReferences = false;
DisplaySettings.bShowSockets = false;
DisplaySettings.bShowComponents = false;
DisplaySettings.bHideParentsOnFilter = true;
DisplaySettings.bFlattenHierarchyOnFilter = true;
DisplaySettings.bShowIconColors = true;
DisplaySettings.bArrangeByModules = false;
DisplaySettings.bFlattenModules = false;
DisplaySettings.NameDisplayMode = EElementNameDisplayMode::AssetDefault;
DisplaySettings.OutlinerDisplayMode = EMultiRigTreeDisplayMode::All;
const TSharedRef<SHorizontalBox> HeaderContentWidget = SNew(SHorizontalBox);
HeaderContentWidget->AddSlot()
.HAlign(HAlign_Right)
[
SAssignNew(AddSpaceMenuAnchor, SMenuAnchor)
.Placement( MenuPlacement_BelowAnchor )
.OnGetMenuContent( this, &FRigControlElementDetails::GetAddSpaceContent, PropertyUtilities)
.Content()
[
SNew(SImage)
.OnMouseButtonDown(this, &FRigControlElementDetails::OnAddSpaceMouseDown, PropertyUtilities)
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
Category.HeaderContent(HeaderContentWidget);
TArray<FRigElementKeyWithLabel> AvailableSpaces;
const FRigElementKey DefaultParent = Hierarchy->GetDefaultParent(ControlElement->GetKey());
if(DefaultParent.IsValid())
{
const FName SpaceLabel = Hierarchy->GetDisplayLabelForParent(ControlElement->GetKey(), DefaultParent);
AvailableSpaces.Emplace(DefaultParent, SpaceLabel);
}
for(const FRigElementKeyWithLabel& AvailableSpace : ControlElement->Settings.Customization.AvailableSpaces)
{
AvailableSpaces.AddUnique(AvailableSpace);
}
static const FText RemoveSpaceText = LOCTEXT("RemoveSpace", "Remove Space");
static const FText RemoveChannelHostText = LOCTEXT("RemoveChannelHost", "Remove Channel Host");
static const FText RemoveSpaceToolTipText = LOCTEXT("RemoveSpaceToolTip", "Removes this space from the list of available spaces");
static const FText RemoveChannelHostToolTipText = LOCTEXT("RemoveChannelHostToolTip", "Remove the channel from this hosting control");
for(int32 Index = 0; Index < AvailableSpaces.Num(); Index++)
{
const FRigElementKey ControlKey = ControlElement->GetKey();
const FRigElementKeyWithLabel AvailableSpace = AvailableSpaces[Index];
const bool bIsParentSpace = Index == 0;
const TPair<const FSlateBrush*, FSlateColor> BrushAndColor = SRigHierarchyItem::GetBrushForElementType(Hierarchy, AvailableSpace.Key);
TSharedPtr<SButton> SelectSpaceButton, RemoveSpaceButton, MoveSpaceUpButton, MoveSpaceDownButton;
TSharedPtr<SImage> SelectSpaceImage, RemoveSpaceImage, MoveSpaceUpImage, MoveSpaceDownImage;
FDetailWidgetRow& WidgetRow = Category.AddCustomRow(FText::FromString(AvailableSpace.Key.ToString()))
.NameContent()
.MinDesiredWidth(200.f)
.MaxDesiredWidth(800.f)
[
SNew(SHorizontalBox)
.IsEnabled(bIsEnabled)
+ SHorizontalBox::Slot()
.MaxWidth(32)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 3.f, 0.f))
[
SNew(SImage)
.Image(BrushAndColor.Key)
.ColorAndOpacity(BrushAndColor.Value)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 8.f, 0.f))
[
SNew(SEditableText)
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsReadOnly((Index == 0) && bIsEnabled)
.Text_Lambda([this, AvailableSpace]() -> FText
{
const FName Label = PerElementInfos[0].GetHierarchy()->GetDisplayLabelForParent(
PerElementInfos[0].Element.GetKey(),
AvailableSpace.Key);
if(Label.IsNone())
{
return GetDisplayNameForElement(AvailableSpace.Key);
}
return FText::FromName(Label);
})
.OnTextCommitted_Lambda([this, AvailableSpace](const FText& InText, ETextCommit::Type InCommitType)
{
if(InCommitType == ETextCommit::OnCleared)
{
return;
}
UControlRigBlueprint* Blueprint = PerElementInfos[0].GetBlueprint();
URigHierarchyController* Controller = Blueprint->GetHierarchyController();
(void)Controller->SetAvailableSpaceLabel(PerElementInfos[0].Element.GetKey(), AvailableSpace.Key, *InText.ToString(), true);
})
.ToolTipText_Lambda([this, Index, AvailableSpace]()
{
if(Index == 0)
{
return FText::Format(
LOCTEXT("AvailableSpaceToolTipDefaultParentFormat", "{0}\n\nThis is the default parent - the label cannot be edited."),
GetDisplayNameForElement(AvailableSpace.Key));
}
return FText::Format(
LOCTEXT("AvailableSpaceToolTipFormat", "{0}\n\nDouble-click here to edit the label of this space."),
GetDisplayNameForElement(AvailableSpace.Key));
})
]
+SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 0.f, 0.f))
[
SAssignNew(SelectSpaceButton, SButton)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
.OnClicked_Lambda([this, AvailableSpace]() -> FReply
{
return OnSelectElementClicked(AvailableSpace.Key);
})
.ContentPadding(0)
.ToolTipText(LOCTEXT("SelectElementInHierarchy", "Select Element in hierarchy"))
[
SAssignNew(SelectSpaceImage, SImage)
.Image(FAppStyle::GetBrush("Icons.Search"))
]
]
]
.ValueContent()
[
SNew(SHorizontalBox)
.IsEnabled(bIsEnabled)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SAssignNew(MoveSpaceUpButton, SButton)
.Visibility((Index > 0 && !bIsAnimationChannel) ? EVisibility::Visible : EVisibility::Collapsed)
.ButtonStyle(FAppStyle::Get(), TEXT("SimpleButton"))
.ContentPadding(0)
.IsEnabled(Index > 1)
.OnClicked_Lambda([this, ControlKey, AvailableSpace, HierarchyToChange, Index, PropertyUtilities]
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("MoveAvailableSpaceUp", "Move Available Space Up"));
HierarchyToChange->Modify();
Controller->SetAvailableSpaceIndex(ControlKey, AvailableSpace.Key, Index - 2);
PropertyUtilities->ForceRefresh();
}
return FReply::Handled();
})
.ToolTipText(LOCTEXT("MoveUp", "Move Up"))
[
SAssignNew(MoveSpaceUpImage, SImage)
.Image(FAppStyle::GetBrush("Icons.ChevronUp"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SAssignNew(MoveSpaceDownButton, SButton)
.Visibility((Index > 0 && !bIsAnimationChannel) ? EVisibility::Visible : EVisibility::Collapsed)
.ButtonStyle(FAppStyle::Get(), TEXT("SimpleButton"))
.ContentPadding(0)
.IsEnabled(Index > 0 && Index < AvailableSpaces.Num() - 1)
.OnClicked_Lambda([this, ControlKey, AvailableSpace, HierarchyToChange, Index, PropertyUtilities]
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("MoveAvailableSpaceDown", "Move Available Space Down"));
HierarchyToChange->Modify();
Controller->SetAvailableSpaceIndex(ControlKey, AvailableSpace.Key, Index);
PropertyUtilities->ForceRefresh();
}
return FReply::Handled();
})
.ToolTipText(LOCTEXT("MoveDown", "Move Down"))
[
SAssignNew(MoveSpaceDownImage, SImage)
.Image(FAppStyle::GetBrush("Icons.ChevronDown"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SAssignNew(RemoveSpaceButton, SButton)
.Visibility(Index > 0 ? EVisibility::Visible : EVisibility::Collapsed)
.ButtonStyle(FAppStyle::Get(), TEXT("SimpleButton"))
.ContentPadding(0)
.IsEnabled(Index > 0)
.OnClicked_Lambda([this, ControlKey, AvailableSpace, HierarchyToChange, Index, PropertyUtilities]
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
const bool bIsAnimationChannel = IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationChannel);
FScopedTransaction Transaction(bIsAnimationChannel ? RemoveChannelHostText : RemoveSpaceText);
HierarchyToChange->Modify();
if(bIsAnimationChannel)
{
Controller->RemoveChannelHost(ControlKey, AvailableSpace.Key);
}
else
{
Controller->RemoveAvailableSpace(ControlKey, AvailableSpace.Key);
}
PropertyUtilities->ForceRefresh();
}
return FReply::Handled();
})
.ToolTipText(LOCTEXT("Remove", "Remove"))
[
SAssignNew(RemoveSpaceImage, SImage)
.Image(FAppStyle::GetBrush("Icons.Delete"))
]
]
];
SelectSpaceImage->SetColorAndOpacity(TAttribute<FSlateColor>::CreateLambda([this, SelectSpaceButton]() { return FRigElementKeyDetails::OnGetWidgetForeground(SelectSpaceButton); }));
MoveSpaceUpImage->SetColorAndOpacity(TAttribute<FSlateColor>::CreateLambda([this, MoveSpaceUpButton]() { return FRigElementKeyDetails::OnGetWidgetForeground(MoveSpaceUpButton); }));
MoveSpaceDownImage->SetColorAndOpacity(TAttribute<FSlateColor>::CreateLambda([this, MoveSpaceDownButton]() { return FRigElementKeyDetails::OnGetWidgetForeground(MoveSpaceDownButton); }));
RemoveSpaceImage->SetColorAndOpacity(TAttribute<FSlateColor>::CreateLambda([this, RemoveSpaceButton]() { return FRigElementKeyDetails::OnGetWidgetForeground(RemoveSpaceButton); }));
if(!bIsProcedural)
{
if(!bIsAnimationChannel)
{
WidgetRow.AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ControlKey, AvailableSpace, HierarchyToChange, Index, PropertyUtilities]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("MoveAvailableSpaceUp", "Move Available Space Up"));
HierarchyToChange->Modify();
Controller->SetAvailableSpaceIndex(ControlKey, AvailableSpace.Key, Index - 2);
PropertyUtilities->ForceRefresh();
}
}),
FCanExecuteAction::CreateLambda([Index](){ return Index > 1; })),
LOCTEXT("MoveUp", "Move Up"),
LOCTEXT("MoveAvailableSpaceUpToolTip", "Moves this available space up in the list of spaces"),
FSlateIcon());
const int32 NumSpaces = AvailableSpaces.Num();
WidgetRow.AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ControlKey, AvailableSpace, HierarchyToChange, Index, PropertyUtilities]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
FScopedTransaction Transaction(LOCTEXT("MoveAvailableSpaceDown", "Move Available Space Down"));
HierarchyToChange->Modify();
Controller->SetAvailableSpaceIndex(ControlKey, AvailableSpace.Key, Index);
PropertyUtilities->ForceRefresh();
}
}),
FCanExecuteAction::CreateLambda([Index, NumSpaces](){ return Index > 0 && Index < NumSpaces - 1; })),
LOCTEXT("MoveDown", "Move Down"),
LOCTEXT("MoveAvailableSpaceDownToolTip", "Moves this available space down in the list of spaces"),
FSlateIcon());
}
WidgetRow.AddCustomContextMenuAction(FUIAction(
FExecuteAction::CreateLambda([this, ControlKey, AvailableSpace, HierarchyToChange, PropertyUtilities]()
{
if(URigHierarchyController* Controller = HierarchyToChange->GetController(true))
{
const bool bIsAnimationChannel = IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationChannel);
FScopedTransaction Transaction(bIsAnimationChannel ? RemoveChannelHostText : RemoveSpaceText);
HierarchyToChange->Modify();
if(bIsAnimationChannel)
{
Controller->RemoveChannelHost(ControlKey, AvailableSpace.Key);
}
else
{
Controller->RemoveAvailableSpace(ControlKey, AvailableSpace.Key);
}
PropertyUtilities->ForceRefresh();
}
}),
FCanExecuteAction::CreateLambda([bIsParentSpace](){ return !bIsParentSpace; })),
bIsAnimationChannel ? RemoveChannelHostText : RemoveSpaceText,
bIsAnimationChannel ? RemoveChannelHostToolTipText : RemoveSpaceToolTipText,
FSlateIcon());
}
}
Category.InitiallyCollapsed(AvailableSpaces.Num() < 2);
if(AvailableSpaces.IsEmpty())
{
static const FText NoSpacesText = LOCTEXT("NoSpacesSet", "No Available Spaces set");
static const FText NoChannelHostsText = LOCTEXT("NoChannelHostsSet", "No Channel Hosts set");
Category.AddCustomRow(FText()).WholeRowContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 0.f, 0.f))
[
SNew(STextBlock)
.IsEnabled(bIsEnabled)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(bIsAnimationChannel ? NoChannelHostsText : NoSpacesText)
]
];
}
}
FReply FRigControlElementDetails::OnAddAnimationChannelClicked()
{
if(IsAnyElementNotOfType(ERigElementType::Control) || IsAnyElementProcedural())
{
return FReply::Handled();
}
const FRigElementKey Key = PerElementInfos[0].GetElement()->GetKey();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
static const FName ChannelName = TEXT("Channel");
FRigControlSettings Settings;
Settings.AnimationType = ERigControlAnimationType::AnimationChannel;
Settings.ControlType = ERigControlType::Float;
Settings.MinimumValue = FRigControlValue::Make<float>(0.f);
Settings.MaximumValue = FRigControlValue::Make<float>(1.f);
Settings.DisplayName = HierarchyToChange->GetSafeNewDisplayName(Key, ChannelName);
HierarchyToChange->GetController(true)->AddAnimationChannel(ChannelName, Key, Settings, true, true);
HierarchyToChange->GetController(true)->SelectElement(Key);
return FReply::Handled();
}
TSharedRef<ITableRow> FRigControlElementDetails::HandleGenerateAnimationChannelTypeRow(TSharedPtr<ERigControlType> ControlType, const TSharedRef<STableViewBase>& OwnerTable, FRigElementKey ControlKey)
{
const URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
TPair<const FSlateBrush*, FSlateColor> BrushAndColor = SRigHierarchyItem::GetBrushForElementType(HierarchyToChange, ControlKey);
BrushAndColor.Value = SRigHierarchyItem::GetColorForControlType(*ControlType.Get(), nullptr);
return SNew(STableRow<TSharedPtr<ERigControlType>>, OwnerTable)
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.MaxWidth(18)
.FillWidth(1.0)
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 3.f, 0.f))
[
SNew(SImage)
.Image(BrushAndColor.Key)
.ColorAndOpacity(BrushAndColor.Value)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(FMargin(0.f, 0.f, 0.f, 0.f))
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(StaticEnum<ERigControlType>()->GetDisplayNameTextByValue((int64)*ControlType.Get()))
]
];
}
TSharedRef<SWidget> FRigControlElementDetails::GetAddSpaceContent(const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
if(PerElementInfos.IsEmpty())
{
return SNullWidget::NullWidget;
}
FRigTreeDelegates RigTreeDelegates;
RigTreeDelegates.OnGetHierarchy = FOnGetRigTreeHierarchy::CreateLambda([this]()
{
return PerElementInfos[0].GetHierarchy();
});
RigTreeDelegates.OnGetDisplaySettings = FOnGetRigTreeDisplaySettings::CreateSP(this, &FRigControlElementDetails::GetDisplaySettings);
RigTreeDelegates.OnGetSelection = FOnRigTreeGetSelection::CreateLambda([]() -> TArray<FRigHierarchyKey> { return {}; });
RigTreeDelegates.OnSelectionChanged = FOnRigTreeSelectionChanged::CreateSP(this, &FRigControlElementDetails::OnAddSpaceSelection, PropertyUtilities);
return SNew(SBox)
.Padding(2.5)
.MinDesiredWidth(200)
.MinDesiredHeight(300)
[
SNew(SRigHierarchyTreeView)
.RigTreeDelegates(RigTreeDelegates)
.PopulateOnConstruct(true)
];
}
FReply FRigControlElementDetails::OnAddSpaceMouseDown(const FGeometry& InGeometry, const FPointerEvent& InPointerEvent, const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
AddSpaceMenuAnchor->SetIsOpen(true);
return FReply::Handled();
}
void FRigControlElementDetails::OnAddSpaceSelection(TSharedPtr<FRigTreeElement> Selection, ESelectInfo::Type SelectInfo, const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
if(Selection)
{
const FRigElementKey ChildKey = PerElementInfos[0].GetElement()->GetKey();
const FRigElementKey NewParentKey = Selection->Key.GetElement();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
AddSpaceMenuAnchor->SetIsOpen(false);
const bool bIsAnimationChannel = IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationChannel);
static const FText AddSpaceText = LOCTEXT("AddSpace", "Add Space");
static const FText AddChannelHostText = LOCTEXT("AddChannelHost", "Add Channel Host");
FScopedTransaction Transaction(bIsAnimationChannel ? AddChannelHostText : AddSpaceText);
HierarchyToChange->Modify();
if(bIsAnimationChannel)
{
HierarchyToChange->GetController(true)->AddChannelHost(ChildKey, NewParentKey);
}
else
{
HierarchyToChange->GetController(true)->AddAvailableSpace(ChildKey, NewParentKey);
}
PropertyUtilities->ForceRefresh();
}
}
void FRigControlElementDetails::HandleControlTypeChanged(TSharedPtr<ERigControlType> ControlType, ESelectInfo::Type SelectInfo, FRigElementKey ControlKey, const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
HandleControlTypeChanged(*ControlType.Get(), {ControlKey}, PropertyUtilities);
}
void FRigControlElementDetails::HandleControlTypeChanged(ERigControlType ControlType, TArray<FRigElementKey> ControlKeys, const TSharedRef<IPropertyUtilities> PropertyUtilities)
{
if(PerElementInfos.IsEmpty())
{
return;
}
if(ControlKeys.IsEmpty())
{
for(const FPerElementInfo& Info : PerElementInfos)
{
ControlKeys.Add(Info.GetDefaultElement<FRigControlElement>()->GetKey());
}
}
for(const FRigElementKey& ControlKey : ControlKeys)
{
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
HierarchyToChange->Modify();
FRigControlElement* ControlElement = HierarchyToChange->FindChecked<FRigControlElement>(ControlKey);
FRigControlValue ValueToSet;
ControlElement->Settings.ControlType = ControlType;
ControlElement->Settings.LimitEnabled.Reset();
ControlElement->Settings.bGroupWithParentControl = false;
ControlElement->Settings.FilteredChannels.Reset();
switch (ControlElement->Settings.ControlType)
{
case ERigControlType::Bool:
{
ControlElement->Settings.AnimationType = ERigControlAnimationType::AnimationChannel;
ValueToSet = FRigControlValue::Make<bool>(false);
ControlElement->Settings.bGroupWithParentControl = ControlElement->Settings.IsAnimatable();
break;
}
case ERigControlType::Float:
{
ValueToSet = FRigControlValue::Make<float>(0.f);
ControlElement->Settings.SetupLimitArrayForType(true);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<float>(0.f);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<float>(100.f);
ControlElement->Settings.bGroupWithParentControl = ControlElement->Settings.IsAnimatable();
break;
}
case ERigControlType::ScaleFloat:
{
ValueToSet = FRigControlValue::Make<float>(1.f);
ControlElement->Settings.SetupLimitArrayForType(false);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<float>(0.f);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<float>(10.f);
ControlElement->Settings.bGroupWithParentControl = ControlElement->Settings.IsAnimatable();
break;
}
case ERigControlType::Integer:
{
ValueToSet = FRigControlValue::Make<int32>(0);
ControlElement->Settings.SetupLimitArrayForType(true);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<int32>(0);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<int32>(100);
ControlElement->Settings.bGroupWithParentControl = ControlElement->Settings.IsAnimatable();
break;
}
case ERigControlType::Vector2D:
{
ValueToSet = FRigControlValue::Make<FVector2D>(FVector2D::ZeroVector);
ControlElement->Settings.SetupLimitArrayForType(true);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<FVector2D>(FVector2D::ZeroVector);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<FVector2D>(FVector2D(100.f, 100.f));
ControlElement->Settings.bGroupWithParentControl = ControlElement->Settings.IsAnimatable();
break;
}
case ERigControlType::Position:
{
ValueToSet = FRigControlValue::Make<FVector>(FVector::ZeroVector);
ControlElement->Settings.SetupLimitArrayForType(false);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<FVector>(-FVector::OneVector);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<FVector>(FVector::OneVector);
break;
}
case ERigControlType::Scale:
{
ValueToSet = FRigControlValue::Make<FVector>(FVector::OneVector);
ControlElement->Settings.SetupLimitArrayForType(false);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<FVector>(FVector::ZeroVector);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<FVector>(FVector::OneVector);
break;
}
case ERigControlType::Rotator:
{
ValueToSet = FRigControlValue::Make<FRotator>(FRotator::ZeroRotator);
ControlElement->Settings.SetupLimitArrayForType(false, false);
ControlElement->Settings.MinimumValue = FRigControlValue::Make<FRotator>(FRotator::ZeroRotator);
ControlElement->Settings.MaximumValue = FRigControlValue::Make<FRotator>(FRotator(180.f, 180.f, 180.f));
break;
}
case ERigControlType::Transform:
{
ValueToSet = FRigControlValue::Make<FTransform>(FTransform::Identity);
ControlElement->Settings.SetupLimitArrayForType(false, false, false);
ControlElement->Settings.MinimumValue = ValueToSet;
ControlElement->Settings.MaximumValue = ValueToSet;
break;
}
case ERigControlType::TransformNoScale:
{
FTransformNoScale Identity = FTransform::Identity;
ValueToSet = FRigControlValue::Make<FTransformNoScale>(Identity);
ControlElement->Settings.SetupLimitArrayForType(false, false, false);
ControlElement->Settings.MinimumValue = ValueToSet;
ControlElement->Settings.MaximumValue = ValueToSet;
break;
}
case ERigControlType::EulerTransform:
{
FEulerTransform Identity = FEulerTransform::Identity;
ValueToSet = FRigControlValue::Make<FEulerTransform>(Identity);
ControlElement->Settings.SetupLimitArrayForType(false, false, false);
ControlElement->Settings.MinimumValue = ValueToSet;
ControlElement->Settings.MaximumValue = ValueToSet;
break;
}
default:
{
ensure(false);
break;
}
}
HierarchyToChange->SetControlSettings(ControlElement, ControlElement->Settings, true, true, true);
HierarchyToChange->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Initial, true, false, true);
HierarchyToChange->SetControlValue(ControlElement, ValueToSet, ERigControlValueType::Current, true, false, true);
for(const FPerElementInfo& Info : PerElementInfos)
{
if(Info.Element.Get()->GetKey() == ControlKey)
{
Info.WrapperObject->SetContent<FRigControlElement>(*ControlElement);
}
}
if (HierarchyToChange != Hierarchy)
{
if(FRigControlElement* OtherControlElement = Hierarchy->Find<FRigControlElement>(ControlKey))
{
OtherControlElement->Settings = ControlElement->Settings;
Hierarchy->SetControlSettings(OtherControlElement, OtherControlElement->Settings, true, true, true);
Hierarchy->SetControlValue(OtherControlElement, ValueToSet, ERigControlValueType::Initial, true);
Hierarchy->SetControlValue(OtherControlElement, ValueToSet, ERigControlValueType::Current, true);
}
}
else
{
PerElementInfos[0].GetBlueprint()->PropagateHierarchyFromBPToInstances();
}
}
PropertyUtilities->ForceRefresh();
}
void FRigControlElementDetails::CustomizeShape(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
if(ContainsElementByPredicate([](const FPerElementInfo& Info)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return !ControlElement->Settings.SupportsShape();
}
return true;
}))
{
return;
}
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural;
ShapeNameList.Reset();
if (UControlRigBlueprint* Blueprint = PerElementInfos[0].GetBlueprint())
{
if(UEdGraph* RootEdGraph = Blueprint->GetEdGraph(Blueprint->GetModel()))
{
if(UControlRigGraph* RigGraph = Cast<UControlRigGraph>(RootEdGraph))
{
URigHierarchy* Hierarchy = Blueprint->Hierarchy;
if(UControlRig* RigBeingDebugged = Cast<UControlRig>(Blueprint->GetObjectBeingDebugged()))
{
Hierarchy = RigBeingDebugged->GetHierarchy();
}
const TArray<TSoftObjectPtr<UControlRigShapeLibrary>>* ShapeLibraries = &Blueprint->ShapeLibraries;
if(const UControlRig* DebuggedControlRig = Hierarchy->GetTypedOuter<UControlRig>())
{
ShapeLibraries = &DebuggedControlRig->GetShapeLibraries();
}
RigGraph->CacheNameLists(Hierarchy, &Blueprint->DrawContainer, *ShapeLibraries);
if(const TArray<TSharedPtr<FRigVMStringWithTag>>* GraphShapeNameListPtr = RigGraph->GetShapeNameList())
{
ShapeNameList = *GraphShapeNameListPtr;
}
}
}
if(ShapeNameList.IsEmpty())
{
const bool bUseNameSpace = Blueprint->ShapeLibraries.Num() > 1;
for(TSoftObjectPtr<UControlRigShapeLibrary>& ShapeLibrary : Blueprint->ShapeLibraries)
{
if (!ShapeLibrary.IsValid())
{
ShapeLibrary.LoadSynchronous();
}
if (ShapeLibrary.IsValid())
{
const FString NameSpace = bUseNameSpace ? ShapeLibrary->GetName() + TEXT(".") : FString();
ShapeNameList.Add(MakeShared<FRigVMStringWithTag>(NameSpace + ShapeLibrary->DefaultShape.ShapeName.ToString()));
for (const FControlRigShapeDefinition& Shape : ShapeLibrary->Shapes)
{
ShapeNameList.Add(MakeShared<FRigVMStringWithTag>(NameSpace + Shape.ShapeName.ToString()));
}
}
}
}
}
IDetailCategoryBuilder& ShapeCategory = DetailBuilder.EditCategory(TEXT("Shape"), LOCTEXT("Shape", "Shape"));
const TSharedPtr<IPropertyHandle> SettingsHandle = DetailBuilder.GetProperty(TEXT("Settings"));
if(!IsAnyControlNotOfAnimationType(ERigControlAnimationType::ProxyControl))
{
ShapeCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("ShapeVisibility")).ToSharedRef())
.IsEnabled(bIsEnabled)
.DisplayName(FText::FromString(TEXT("Visibility Mode")));
}
ShapeCategory.AddProperty(SettingsHandle->GetChildHandle(TEXT("bShapeVisible")).ToSharedRef())
.IsEnabled(bIsEnabled)
.DisplayName(FText::FromString(TEXT("Visible")));
IDetailGroup& ShapePropertiesGroup = ShapeCategory.AddGroup(TEXT("Shape Properties"), LOCTEXT("ShapeProperties", "Shape Properties"));
ShapePropertiesGroup.HeaderRow()
.IsEnabled(bIsEnabled)
.NameContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LOCTEXT("ShapeProperties", "Shape Properties"))
.ToolTipText(LOCTEXT("ShapePropertiesTooltip", "Customize the properties of the shape"))
]
.CopyAction(FUIAction(
FExecuteAction::CreateSP(this, &FRigControlElementDetails::OnCopyShapeProperties)))
.PasteAction(FUIAction(
FExecuteAction::CreateSP(this, &FRigControlElementDetails::OnPasteShapeProperties),
FCanExecuteAction::CreateLambda([bIsEnabled]() { return bIsEnabled; })));
// setup shape transform
SAdvancedTransformInputBox<FEulerTransform>::FArguments TransformWidgetArgs = SAdvancedTransformInputBox<FEulerTransform>::FArguments()
.IsEnabled(bIsEnabled)
.DisplayToggle(false)
.DisplayRelativeWorld(false)
.Font(IDetailLayoutBuilder::GetDetailFont())
.PreventThrottling(true);
TArray<FRigElementKey> Keys = GetElementKeys();
Keys = PerElementInfos[0].GetHierarchy()->SortKeys(Keys);
TWeakPtr<FRigControlElementDetails> WeakThisPtr = StaticCastWeakPtr<FRigControlElementDetails>(AsWeak());
auto GetShapeTransform = [WeakThisPtr](
const FRigElementKey& Key
) -> FEulerTransform
{
if(const TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin())
{
if(const FPerElementInfo& Info = StrongThisPtr->FindElement(Key))
{
if(FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return FEulerTransform(Info.GetHierarchy()->GetControlShapeTransform(ControlElement, ERigTransformType::InitialLocal));
}
}
}
return FEulerTransform::Identity;
};
auto SetShapeTransform = [WeakThisPtr](
const FRigElementKey& Key,
const FEulerTransform& InTransform,
bool bSetupUndo
)
{
if(const TSharedPtr<FRigTransformElementDetails> StrongThisPtr = WeakThisPtr.Pin())
{
if(const FPerElementInfo& Info = StrongThisPtr->FindElement(Key))
{
if(const FRigControlElement* ControlElement = Info.GetDefaultElement<FRigControlElement>())
{
Info.GetDefaultHierarchy()->SetControlShapeTransform((FRigControlElement*)ControlElement, InTransform.ToFTransform(), ERigTransformType::InitialLocal, bSetupUndo, true, bSetupUndo);
}
}
}
};
TransformWidgetArgs.OnGetNumericValue_Lambda([Keys, GetShapeTransform](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent) -> TOptional<FVector::FReal>
{
TOptional<FVector::FReal> FirstValue;
for(int32 Index = 0; Index < Keys.Num(); Index++)
{
const FRigElementKey& Key = Keys[Index];
FEulerTransform Xfo = GetShapeTransform(Key);
TOptional<FVector::FReal> CurrentValue = SAdvancedTransformInputBox<FEulerTransform>::GetNumericValueFromTransform(Xfo, Component, Representation, SubComponent);
if(!CurrentValue.IsSet())
{
return CurrentValue;
}
if(Index == 0)
{
FirstValue = CurrentValue;
}
else
{
if(!FMath::IsNearlyEqual(FirstValue.GetValue(), CurrentValue.GetValue()))
{
return TOptional<FVector::FReal>();
}
}
}
return FirstValue;
});
TransformWidgetArgs.OnNumericValueChanged_Lambda(
[
Keys,
this,
GetShapeTransform,
SetShapeTransform
](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal InNumericValue)
{
for(const FRigElementKey& Key : Keys)
{
FEulerTransform Transform = GetShapeTransform(Key);
FEulerTransform PreviousTransform = Transform;
SAdvancedTransformInputBox<FEulerTransform>::ApplyNumericValueChange(Transform, InNumericValue, Component, Representation, SubComponent);
if(!FRigControlElementDetails::Equals(Transform, PreviousTransform))
{
if(!SliderTransaction.IsValid())
{
SliderTransaction = MakeShareable(new FScopedTransaction(NSLOCTEXT("ControlRigElementDetails", "ChangeNumericValue", "Change Numeric Value")));
PerElementInfos[0].GetDefaultHierarchy()->Modify();
}
SetShapeTransform(Key, Transform, false);
}
}
});
TransformWidgetArgs.OnNumericValueCommitted_Lambda(
[
Keys,
this,
GetShapeTransform,
SetShapeTransform
](
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal InNumericValue,
ETextCommit::Type InCommitType)
{
{
FScopedTransaction Transaction(LOCTEXT("ChangeNumericValue", "Change Numeric Value"));
PerElementInfos[0].GetDefaultHierarchy()->Modify();
for(const FRigElementKey& Key : Keys)
{
FEulerTransform Transform = GetShapeTransform(Key);
FEulerTransform PreviousTransform = Transform;
SAdvancedTransformInputBox<FEulerTransform>::ApplyNumericValueChange(Transform, InNumericValue, Component, Representation, SubComponent);
if(!FRigControlElementDetails::Equals(Transform, PreviousTransform))
{
SetShapeTransform(Key, Transform, true);
}
}
}
SliderTransaction.Reset();
});
TransformWidgetArgs.OnCopyToClipboard_Lambda([Keys, GetShapeTransform](
ESlateTransformComponent::Type InComponent
)
{
if(Keys.Num() == 0)
{
return;
}
const FRigElementKey& FirstKey = Keys[0];
FEulerTransform Xfo = GetShapeTransform(FirstKey);
FString Content;
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
const FVector Data = Xfo.GetLocation();
TBaseStructure<FVector>::Get()->ExportText(Content, &Data, &Data, nullptr, PPF_None, nullptr);
break;
}
case ESlateTransformComponent::Rotation:
{
const FRotator Data = Xfo.Rotator();
TBaseStructure<FRotator>::Get()->ExportText(Content, &Data, &Data, nullptr, PPF_None, nullptr);
break;
}
case ESlateTransformComponent::Scale:
{
const FVector Data = Xfo.GetScale3D();
TBaseStructure<FVector>::Get()->ExportText(Content, &Data, &Data, nullptr, PPF_None, nullptr);
break;
}
case ESlateTransformComponent::Max:
default:
{
TBaseStructure<FEulerTransform>::Get()->ExportText(Content, &Xfo, &Xfo, nullptr, PPF_None, nullptr);
break;
}
}
if(!Content.IsEmpty())
{
FPlatformApplicationMisc::ClipboardCopy(*Content);
}
});
TransformWidgetArgs.OnPasteFromClipboard_Lambda([Keys, GetShapeTransform, SetShapeTransform, this](
ESlateTransformComponent::Type InComponent
)
{
if(Keys.Num() == 0)
{
return;
}
FString Content;
FPlatformApplicationMisc::ClipboardPaste(Content);
if(Content.IsEmpty())
{
return;
}
FScopedTransaction Transaction(LOCTEXT("PasteTransform", "Paste Transform"));
PerElementInfos[0].GetDefaultHierarchy()->Modify();
for(const FRigElementKey& Key : Keys)
{
FEulerTransform Xfo = GetShapeTransform(Key);
{
class FRigPasteTransformWidgetErrorPipe : public FOutputDevice
{
public:
int32 NumErrors;
FRigPasteTransformWidgetErrorPipe()
: FOutputDevice()
, NumErrors(0)
{
}
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
{
UE_LOG(LogControlRig, Error, TEXT("Error Pasting to Widget: %s"), V);
NumErrors++;
}
};
FRigPasteTransformWidgetErrorPipe ErrorPipe;
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
FVector Data = Xfo.GetLocation();
TBaseStructure<FVector>::Get()->ImportText(*Content, &Data, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FVector>::Get()->GetName(), true);
Xfo.SetLocation(Data);
break;
}
case ESlateTransformComponent::Rotation:
{
FRotator Data = Xfo.Rotator();
TBaseStructure<FRotator>::Get()->ImportText(*Content, &Data, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FRotator>::Get()->GetName(), true);
Xfo.SetRotator(Data);
break;
}
case ESlateTransformComponent::Scale:
{
FVector Data = Xfo.GetScale3D();
TBaseStructure<FVector>::Get()->ImportText(*Content, &Data, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FVector>::Get()->GetName(), true);
Xfo.SetScale3D(Data);
break;
}
case ESlateTransformComponent::Max:
default:
{
TBaseStructure<FEulerTransform>::Get()->ImportText(*Content, &Xfo, nullptr, PPF_None, &ErrorPipe, TBaseStructure<FEulerTransform>::Get()->GetName(), true);
break;
}
}
if(ErrorPipe.NumErrors == 0)
{
SetShapeTransform(Key, Xfo, true);
}
}
}
});
TransformWidgetArgs.DiffersFromDefault_Lambda([
Keys,
GetShapeTransform
](
ESlateTransformComponent::Type InComponent) -> bool
{
for(const FRigElementKey& Key : Keys)
{
const FEulerTransform CurrentTransform = GetShapeTransform(Key);
static const FEulerTransform DefaultTransform = FEulerTransform::Identity;
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
if(!DefaultTransform.GetLocation().Equals(CurrentTransform.GetLocation()))
{
return true;
}
break;
}
case ESlateTransformComponent::Rotation:
{
if(!DefaultTransform.Rotator().Equals(CurrentTransform.Rotator()))
{
return true;
}
break;
}
case ESlateTransformComponent::Scale:
{
if(!DefaultTransform.GetScale3D().Equals(CurrentTransform.GetScale3D()))
{
return true;
}
break;
}
default: // also no component whole transform
{
if(!DefaultTransform.GetLocation().Equals(CurrentTransform.GetLocation()) ||
!DefaultTransform.Rotator().Equals(CurrentTransform.Rotator()) ||
!DefaultTransform.GetScale3D().Equals(CurrentTransform.GetScale3D()))
{
return true;
}
break;
}
}
}
return false;
});
TransformWidgetArgs.OnResetToDefault_Lambda([Keys, GetShapeTransform, SetShapeTransform, this](
ESlateTransformComponent::Type InComponent)
{
FScopedTransaction Transaction(LOCTEXT("ResetTransformToDefault", "Reset Transform to Default"));
PerElementInfos[0].GetDefaultHierarchy()->Modify();
for(const FRigElementKey& Key : Keys)
{
FEulerTransform CurrentTransform = GetShapeTransform(Key);
static const FEulerTransform DefaultTransform = FEulerTransform::Identity;
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
CurrentTransform.SetLocation(DefaultTransform.GetLocation());
break;
}
case ESlateTransformComponent::Rotation:
{
CurrentTransform.SetRotator(DefaultTransform.Rotator());
break;
}
case ESlateTransformComponent::Scale:
{
CurrentTransform.SetScale3D(DefaultTransform.GetScale3D());
break;
}
default: // whole transform / max component
{
CurrentTransform = DefaultTransform;
break;
}
}
SetShapeTransform(Key, CurrentTransform, true);
}
});
TArray<FRigControlElement*> ControlElements;
Algo::Transform(PerElementInfos, ControlElements, [](const FPerElementInfo& Info)
{
return Info.GetElement<FRigControlElement>();
});
SAdvancedTransformInputBox<FEulerTransform>::ConstructGroupedTransformRows(
ShapeCategory,
LOCTEXT("ShapeTransform", "Shape Transform"),
LOCTEXT("ShapeTransformTooltip", "The relative transform of the shape under the control"),
TransformWidgetArgs);
ShapeNameHandle = SettingsHandle->GetChildHandle(TEXT("ShapeName"));
ShapePropertiesGroup.AddPropertyRow(ShapeNameHandle.ToSharedRef()).CustomWidget()
.NameContent()
[
SNew(STextBlock)
.IsEnabled(bIsEnabled)
.Text(FText::FromString(TEXT("Shape")))
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(this, &FRigControlElementDetails::IsShapeEnabled)
]
.ValueContent()
[
SAssignNew(ShapeNameListWidget, SControlRigShapeNameList, ControlElements, PerElementInfos[0].GetBlueprint())
.OnGetNameListContent(this, &FRigControlElementDetails::GetShapeNameList)
.IsEnabled(this, &FRigControlElementDetails::IsShapeEnabled)
];
ShapeColorHandle = SettingsHandle->GetChildHandle(TEXT("ShapeColor"));
ShapePropertiesGroup.AddPropertyRow(ShapeColorHandle.ToSharedRef())
.IsEnabled(bIsEnabled)
.DisplayName(FText::FromString(TEXT("Color")));
}
void FRigControlElementDetails::BeginDestroy()
{
FRigTransformElementDetails::BeginDestroy();
if(ShapeNameListWidget.IsValid())
{
ShapeNameListWidget->BeginDestroy();
}
}
void FRigControlElementDetails::RegisterSectionMappings(FPropertyEditorModule& PropertyEditorModule, UClass* InClass)
{
FRigTransformElementDetails::RegisterSectionMappings(PropertyEditorModule, InClass);
TSharedRef<FPropertySection> ControlSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Control", LOCTEXT("Control", "Control"));
ControlSection->AddCategory("General");
ControlSection->AddCategory("Control");
ControlSection->AddCategory("Value");
ControlSection->AddCategory("AnimationChannels");
TSharedRef<FPropertySection> ShapeSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Shape", LOCTEXT("Shape", "Shape"));
ShapeSection->AddCategory("General");
ShapeSection->AddCategory("Shape");
TSharedRef<FPropertySection> ChannelsSection = PropertyEditorModule.FindOrCreateSection(InClass->GetFName(), "Channels", LOCTEXT("Channels", "Channels"));
ChannelsSection->AddCategory("AnimationChannels");
}
bool FRigControlElementDetails::IsShapeEnabled() const
{
if(IsAnyElementProcedural())
{
return false;
}
return ContainsElementByPredicate([](const FPerElementInfo& Info)
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
return ControlElement->Settings.SupportsShape();
}
return false;
});
}
const TArray<TSharedPtr<FRigVMStringWithTag>>& FRigControlElementDetails::GetShapeNameList() const
{
return ShapeNameList;
}
FText FRigControlElementDetails::GetDisplayName() const
{
FName DisplayName(NAME_None);
for(int32 ObjectIndex = 0; ObjectIndex < PerElementInfos.Num(); ObjectIndex++)
{
const FPerElementInfo& Info = PerElementInfos[ObjectIndex];
if(const FRigControlElement* ControlElement = Info.GetDefaultElement<FRigControlElement>())
{
const FName ThisDisplayName =
(ControlElement->IsAnimationChannel()) ?
ControlElement->GetDisplayName() :
ControlElement->Settings.DisplayName;
if(ObjectIndex == 0)
{
DisplayName = ThisDisplayName;
}
else if(DisplayName != ThisDisplayName)
{
return ControlRigDetailsMultipleValues;
}
}
}
if(!DisplayName.IsNone())
{
return FText::FromName(DisplayName);
}
return FText();
}
void FRigControlElementDetails::SetDisplayName(const FText& InNewText, ETextCommit::Type InCommitType)
{
for(int32 ObjectIndex = 0; ObjectIndex < PerElementInfos.Num(); ObjectIndex++)
{
const FPerElementInfo& Info = PerElementInfos[ObjectIndex];
if(const FRigControlElement* ControlElement = Info.GetDefaultElement<FRigControlElement>())
{
SetDisplayNameForElement(InNewText, InCommitType, ControlElement->GetKey());
}
}
}
FText FRigControlElementDetails::GetDisplayNameForElement(const FRigElementKey& InKey) const
{
if(PerElementInfos.IsEmpty())
{
return FText();
}
URigHierarchy* Hierarchy = PerElementInfos[0].GetDefaultHierarchy();
const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(InKey);
if(ControlElement == nullptr)
{
return FText::FromName(InKey.Name);
}
return FText::FromName(ControlElement->GetDisplayName());
}
void FRigControlElementDetails::SetDisplayNameForElement(const FText& InNewText, ETextCommit::Type InCommitType, const FRigElementKey& InKeyToRename)
{
if(InCommitType == ETextCommit::OnCleared)
{
return;
}
if(PerElementInfos.IsEmpty())
{
return;
}
URigHierarchy* Hierarchy = PerElementInfos[0].GetDefaultHierarchy();
const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(InKeyToRename);
if(ControlElement == nullptr)
{
return;
}
if(ControlElement->IsProcedural())
{
return;
}
const FName DisplayName = InNewText.IsEmpty() ? FName(NAME_None) : FName(*InNewText.ToString());
const bool bRename = IsAnyControlOfAnimationType(ERigControlAnimationType::AnimationChannel);
Hierarchy->GetController(true)->SetDisplayName(InKeyToRename, DisplayName, bRename, true, true);
}
bool FRigControlElementDetails::OnVerifyDisplayNameChanged(const FText& InText, FText& OutErrorMessage, const FRigElementKey& InKeyToRename)
{
const FString NewName = InText.ToString();
if (NewName.IsEmpty())
{
OutErrorMessage = FText::FromString(TEXT("Name is empty."));
return false;
}
if(PerElementInfos.IsEmpty())
{
return false;
}
const URigHierarchy* Hierarchy = PerElementInfos[0].GetDefaultHierarchy();
const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(InKeyToRename);
if(ControlElement == nullptr)
{
return false;
}
if(ControlElement->IsProcedural())
{
return false;
}
// make sure there is no duplicate
if(const FRigBaseElement* ParentElement = Hierarchy->GetFirstParent(ControlElement))
{
FString OutErrorString;
if (!Hierarchy->IsDisplayNameAvailable(ParentElement->GetKey(), FRigName(NewName), &OutErrorString))
{
OutErrorMessage = FText::FromString(OutErrorString);
return false;
}
}
return true;
}
void FRigControlElementDetails::OnCopyShapeProperties()
{
FString Value;
if (!PerElementInfos.IsEmpty())
{
if(const FRigControlElement* ControlElement = PerElementInfos[0].GetElement<FRigControlElement>())
{
Value = FString::Printf(TEXT("(ShapeName=\"%s\",ShapeColor=%s)"),
*ControlElement->Settings.ShapeName.ToString(),
*ControlElement->Settings.ShapeColor.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;
bool bSuccessful = FParse::Value(*TrimmedText, TEXT("ShapeName="), ShapeName) &&
FParse::Value(*TrimmedText, TEXT("ShapeColor="), ShapeColorStr, false);
if (bSuccessful)
{
FScopedTransaction Transaction(LOCTEXT("PasteShape", "Paste Shape"));
// Name
{
ShapeNameHandle->NotifyPreChange();
ShapeNameHandle->SetValue(ShapeName);
ShapeNameHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
}
// Color
{
ShapeColorHandle->NotifyPreChange();
TArray<void*> RawDataPtrs;
ShapeColorHandle->AccessRawData(RawDataPtrs);
for (void* RawPtr: RawDataPtrs)
{
bSuccessful &= static_cast<FLinearColor*>(RawPtr)->InitFromString(ShapeColorStr);
if (!bSuccessful)
{
Transaction.Cancel();
return;
}
}
ShapeColorHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
}
}
}
FDetailWidgetRow& FRigControlElementDetails::CreateBoolValueWidgetRow(
const TArray<FRigElementKey>& Keys,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigControlValueType ValueType,
TAttribute<EVisibility> Visibility,
TSharedPtr<SWidget> NameContent)
{
const static TCHAR* TrueText = TEXT("True");
const static TCHAR* FalseText = TEXT("False");
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural || ValueType == ERigControlValueType::Current;
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
if(ValueType == ERigControlValueType::Current)
{
HierarchyToChange = Hierarchy;
}
if(!NameContent.IsValid())
{
SAssignNew(NameContent, STextBlock)
.Text(Label)
.ToolTipText(Tooltip)
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(bIsEnabled);
}
FDetailWidgetRow& WidgetRow = CategoryBuilder.AddCustomRow(Label)
.Visibility(Visibility)
.NameContent()
.MinDesiredWidth(200.f)
.MaxDesiredWidth(800.f)
[
NameContent.ToSharedRef()
]
.ValueContent()
[
SNew(SCheckBox)
.IsChecked_Lambda([ValueType, Keys, Hierarchy]() -> ECheckBoxState
{
const bool FirstValue = Hierarchy->GetControlValue<bool>(Keys[0], ValueType);
for(int32 Index = 1; Index < Keys.Num(); Index++)
{
const bool SecondValue = Hierarchy->GetControlValue<bool>(Keys[Index], ValueType);
if(FirstValue != SecondValue)
{
return ECheckBoxState::Undetermined;
}
}
return FirstValue ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([ValueType, Keys, HierarchyToChange](ECheckBoxState NewState)
{
if(NewState == ECheckBoxState::Undetermined)
{
return;
}
const bool Value = NewState == ECheckBoxState::Checked;
FScopedTransaction Transaction(LOCTEXT("ChangeValue", "Change Value"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<bool>(Value), ValueType, true, true);
}
})
.IsEnabled(bIsEnabled)
]
.CopyAction(FUIAction(
FExecuteAction::CreateLambda([ValueType, Keys, Hierarchy]()
{
const bool FirstValue = Hierarchy->GetControlValue<bool>(Keys[0], ValueType);
FPlatformApplicationMisc::ClipboardCopy(FirstValue ? TrueText : FalseText);
}),
FCanExecuteAction())
)
.PasteAction(FUIAction(
FExecuteAction::CreateLambda([ValueType, Keys, HierarchyToChange]()
{
FString Content;
FPlatformApplicationMisc::ClipboardPaste(Content);
const bool Value = FToBoolHelper::FromCStringWide(*Content);
FScopedTransaction Transaction(LOCTEXT("ChangeValue", "Change Value"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<bool>(Value), ValueType, true, true);
}
}),
FCanExecuteAction::CreateLambda([bIsEnabled]() { return bIsEnabled; }))
)
.OverrideResetToDefault(FResetToDefaultOverride::Create(
TAttribute<bool>::CreateLambda([ValueType, Keys, Hierarchy, bIsEnabled]() -> bool
{
if(!bIsEnabled)
{
return false;
}
const bool FirstValue = Hierarchy->GetControlValue<bool>(Keys[0], ValueType);
const bool ReferenceValue = ValueType == ERigControlValueType::Initial ? false :
Hierarchy->GetControlValue<bool>(Keys[0], ERigControlValueType::Initial);
return FirstValue != ReferenceValue;
}),
FSimpleDelegate::CreateLambda([ValueType, Keys, HierarchyToChange]()
{
FScopedTransaction Transaction(LOCTEXT("ResetValueToDefault", "Reset Value To Default"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
const bool ReferenceValue = ValueType == ERigControlValueType::Initial ? false :
HierarchyToChange->GetControlValue<bool>(Keys[0], ERigControlValueType::Initial);
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<bool>(ReferenceValue), ValueType, true, true);
}
})
));
return WidgetRow;
}
FDetailWidgetRow& FRigControlElementDetails::CreateFloatValueWidgetRow(
const TArray<FRigElementKey>& Keys,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigControlValueType ValueType,
TAttribute<EVisibility> Visibility,
TSharedPtr<SWidget> NameContent)
{
return CreateNumericValueWidgetRow<float>(Keys, CategoryBuilder, Label, Tooltip, ValueType, Visibility, NameContent);
}
FDetailWidgetRow& FRigControlElementDetails::CreateIntegerValueWidgetRow(
const TArray<FRigElementKey>& Keys,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigControlValueType ValueType,
TAttribute<EVisibility> Visibility,
TSharedPtr<SWidget> NameContent)
{
return CreateNumericValueWidgetRow<int32>(Keys, CategoryBuilder, Label, Tooltip, ValueType, Visibility, NameContent);
}
FDetailWidgetRow& FRigControlElementDetails::CreateEnumValueWidgetRow(
const TArray<FRigElementKey>& Keys,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigControlValueType ValueType,
TAttribute<EVisibility> Visibility,
TSharedPtr<SWidget> NameContent)
{
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural || ValueType == ERigControlValueType::Current;
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
if(ValueType == ERigControlValueType::Current)
{
HierarchyToChange = Hierarchy;
}
UEnum* Enum = nullptr;
for(const FRigElementKey& Key : Keys)
{
if(const FPerElementInfo& Info = FindElement(Key))
{
if(const FRigControlElement* ControlElement = Info.GetElement<FRigControlElement>())
{
Enum = ControlElement->Settings.ControlEnum.Get();
if(Enum)
{
break;
}
}
}
else
{
// If the key was not found for selected elements, it might be a child channel of one of the elements
for (const FPerElementInfo& ElementInfo : PerElementInfos)
{
if (const FRigControlElement* ControlElement = ElementInfo.GetElement<FRigControlElement>())
{
const FRigBaseElementChildrenArray Children = Hierarchy->GetChildren(ControlElement, false);
if (FRigBaseElement* const* Child = Children.FindByPredicate([Key](const FRigBaseElement* Info)
{
return Info->GetKey() == Key;
}))
{
if (const FRigControlElement* ChildElement = Cast<FRigControlElement>(*Child))
{
Enum = ChildElement->Settings.ControlEnum.Get();
if(Enum)
{
break;
}
}
}
}
}
if(Enum)
{
break;
}
}
}
check(Enum != nullptr);
if(!NameContent.IsValid())
{
SAssignNew(NameContent, STextBlock)
.Text(Label)
.ToolTipText(Tooltip)
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(bIsEnabled);
}
FDetailWidgetRow& WidgetRow = CategoryBuilder.AddCustomRow(Label)
.Visibility(Visibility)
.NameContent()
.MinDesiredWidth(200.f)
.MaxDesiredWidth(800.f)
[
NameContent.ToSharedRef()
]
.ValueContent()
[
SNew(SEnumComboBox, Enum)
.CurrentValue_Lambda([ValueType, Keys, Hierarchy]() -> int32
{
const int32 FirstValue = Hierarchy->GetControlValue<int32>(Keys[0], ValueType);
for(int32 Index = 1; Index < Keys.Num(); Index++)
{
const int32 SecondValue = Hierarchy->GetControlValue<int32>(Keys[Index], ValueType);
if(FirstValue != SecondValue)
{
return INDEX_NONE;
}
}
return FirstValue;
})
.OnEnumSelectionChanged_Lambda([ValueType, Keys, HierarchyToChange](int32 NewSelection, ESelectInfo::Type)
{
if(NewSelection == INDEX_NONE)
{
return;
}
FScopedTransaction Transaction(LOCTEXT("ChangeValue", "Change Value"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<int32>(NewSelection), ValueType, true, true);
}
})
.Font(FAppStyle::GetFontStyle(TEXT("MenuItem.Font")))
.IsEnabled(bIsEnabled)
]
.CopyAction(FUIAction(
FExecuteAction::CreateLambda([ValueType, Keys, Hierarchy]()
{
const int32 FirstValue = Hierarchy->GetControlValue<int32>(Keys[0], ValueType);
FPlatformApplicationMisc::ClipboardCopy(*FString::FromInt(FirstValue));
}),
FCanExecuteAction())
)
.PasteAction(FUIAction(
FExecuteAction::CreateLambda([ValueType, Keys, HierarchyToChange]()
{
FString Content;
FPlatformApplicationMisc::ClipboardPaste(Content);
if(!Content.IsNumeric())
{
return;
}
const int32 Value = FCString::Atoi(*Content);
FScopedTransaction Transaction(LOCTEXT("ChangeValue", "Change Value"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<int32>(Value), ValueType, true, true);
}
}),
FCanExecuteAction::CreateLambda([bIsEnabled]() { return bIsEnabled; }))
)
.OverrideResetToDefault(FResetToDefaultOverride::Create(
TAttribute<bool>::CreateLambda([ValueType, Keys, Hierarchy, bIsEnabled]() -> bool
{
if(!bIsEnabled)
{
return false;
}
const int32 FirstValue = Hierarchy->GetControlValue<int32>(Keys[0], ValueType);
const int32 ReferenceValue = ValueType == ERigControlValueType::Initial ? 0 :
Hierarchy->GetControlValue<int32>(Keys[0], ERigControlValueType::Initial);
return FirstValue != ReferenceValue;
}),
FSimpleDelegate::CreateLambda([ValueType, Keys, HierarchyToChange]()
{
FScopedTransaction Transaction(LOCTEXT("ResetValueToDefault", "Reset Value To Default"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
const int32 ReferenceValue = ValueType == ERigControlValueType::Initial ? 0 :
HierarchyToChange->GetControlValue<int32>(Keys[0], ERigControlValueType::Initial);
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<int32>(ReferenceValue), ValueType, true, true);
}
})
));
return WidgetRow;
}
FDetailWidgetRow& FRigControlElementDetails::CreateVector2DValueWidgetRow(
const TArray<FRigElementKey>& Keys,
IDetailCategoryBuilder& CategoryBuilder,
const FText& Label,
const FText& Tooltip,
ERigControlValueType ValueType,
TAttribute<EVisibility> Visibility,
TSharedPtr<SWidget> NameContent)
{
const bool bIsProcedural = IsAnyElementProcedural();
const bool bIsEnabled = !bIsProcedural || ValueType == ERigControlValueType::Current;
const bool bShowToggle = (ValueType == ERigControlValueType::Minimum) || (ValueType == ERigControlValueType::Maximum);
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
URigHierarchy* HierarchyToChange = PerElementInfos[0].GetDefaultHierarchy();
if(ValueType == ERigControlValueType::Current)
{
HierarchyToChange = Hierarchy;
}
using SNumericVector2DInputBox = SNumericVectorInputBox<float, FVector2f, 2>;
TSharedPtr<SNumericVector2DInputBox> VectorInputBox;
FDetailWidgetRow& WidgetRow = CategoryBuilder.AddCustomRow(Label);
TAttribute<ECheckBoxState> ToggleXChecked, ToggleYChecked;
FOnCheckStateChanged OnToggleXChanged, OnToggleYChanged;
if(bShowToggle)
{
auto ToggleChecked = [ValueType, Keys, Hierarchy](int32 Index) -> ECheckBoxState
{
TOptional<bool> FirstValue;
for(const FRigElementKey& Key : Keys)
{
if(const FRigControlElement* ControlElement = Hierarchy->Find<FRigControlElement>(Key))
{
if(ControlElement->Settings.LimitEnabled.Num() == 2)
{
const bool Value = ControlElement->Settings.LimitEnabled[Index].GetForValueType(ValueType);
if(FirstValue.IsSet())
{
if(FirstValue.GetValue() != Value)
{
return ECheckBoxState::Undetermined;
}
}
else
{
FirstValue = Value;
}
}
}
}
if(!ensure(FirstValue.IsSet()))
{
return ECheckBoxState::Undetermined;
}
return FirstValue.GetValue() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
};
ToggleXChecked = TAttribute<ECheckBoxState>::CreateLambda([ToggleChecked]() -> ECheckBoxState
{
return ToggleChecked(0);
});
ToggleYChecked = TAttribute<ECheckBoxState>::CreateLambda([ToggleChecked]() -> ECheckBoxState
{
return ToggleChecked(1);
});
auto OnToggleChanged = [ValueType, Keys, HierarchyToChange](ECheckBoxState InValue, int32 Index)
{
if(InValue == ECheckBoxState::Undetermined)
{
return;
}
FScopedTransaction Transaction(LOCTEXT("ChangeLimitToggle", "Change Limit Toggle"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
if(FRigControlElement* ControlElement = HierarchyToChange->Find<FRigControlElement>(Key))
{
if(ControlElement->Settings.LimitEnabled.Num() == 2)
{
ControlElement->Settings.LimitEnabled[Index].SetForValueType(ValueType, InValue == ECheckBoxState::Checked);
HierarchyToChange->SetControlSettings(ControlElement, ControlElement->Settings, true, true, true);
}
}
}
};
OnToggleXChanged = FOnCheckStateChanged::CreateLambda([OnToggleChanged](ECheckBoxState InValue)
{
OnToggleChanged(InValue, 0);
});
OnToggleYChanged = FOnCheckStateChanged::CreateLambda([OnToggleChanged](ECheckBoxState InValue)
{
OnToggleChanged(InValue, 1);
});
}
auto GetValue = [ValueType, Keys, Hierarchy](int32 Component) -> TOptional<float>
{
const float FirstValue = Hierarchy->GetControlValue<FVector3f>(Keys[0], ValueType).Component(Component);
for(int32 Index = 1; Index < Keys.Num(); Index++)
{
const float SecondValue = Hierarchy->GetControlValue<FVector3f>(Keys[Index], ValueType).Component(Component);
if(FirstValue != SecondValue)
{
return TOptional<float>();
}
}
return FirstValue;
};
auto OnValueChanged = [ValueType, Keys, Hierarchy, HierarchyToChange, this]
(TOptional<float> InValue, ETextCommit::Type InCommitType, bool bSetupUndo, int32 Component)
{
if(!InValue.IsSet())
{
return;
}
const float Value = InValue.GetValue();
for(const FRigElementKey& Key : Keys)
{
FVector3f Vector = Hierarchy->GetControlValue<FVector3f>(Key, ValueType);
if(!FMath::IsNearlyEqual(Vector.Component(Component), Value))
{
if(!SliderTransaction.IsValid())
{
SliderTransaction = MakeShareable(new FScopedTransaction(NSLOCTEXT("ControlRigElementDetails", "ChangeValue", "Change Value")));
HierarchyToChange->Modify();
}
Vector.Component(Component) = Value;
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<FVector3f>(Vector), ValueType, bSetupUndo, bSetupUndo);
};
}
if(bSetupUndo)
{
SliderTransaction.Reset();
}
};
if(!NameContent.IsValid())
{
SAssignNew(NameContent, STextBlock)
.Text(Label)
.ToolTipText(Tooltip)
.Font(IDetailLayoutBuilder::GetDetailFont())
.IsEnabled(bIsEnabled);
}
WidgetRow
.Visibility(Visibility)
.NameContent()
.MinDesiredWidth(200.f)
.MaxDesiredWidth(800.f)
[
NameContent.ToSharedRef()
]
.ValueContent()
[
SAssignNew(VectorInputBox, SNumericVector2DInputBox)
.Font(FAppStyle::GetFontStyle(TEXT("MenuItem.Font")))
.AllowSpin(ValueType == ERigControlValueType::Current || ValueType == ERigControlValueType::Initial)
.SpinDelta(0.01f)
.X_Lambda([GetValue]() -> TOptional<float>
{
return GetValue(0);
})
.Y_Lambda([GetValue]() -> TOptional<float>
{
return GetValue(1);
})
.OnXChanged_Lambda([OnValueChanged](TOptional<float> InValue)
{
OnValueChanged(InValue, ETextCommit::Default, false, 0);
})
.OnYChanged_Lambda([OnValueChanged](TOptional<float> InValue)
{
OnValueChanged(InValue, ETextCommit::Default, false, 1);
})
.OnXCommitted_Lambda([OnValueChanged](TOptional<float> InValue, ETextCommit::Type InCommitType)
{
OnValueChanged(InValue, InCommitType, true, 0);
})
.OnYCommitted_Lambda([OnValueChanged](TOptional<float> InValue, ETextCommit::Type InCommitType)
{
OnValueChanged(InValue, InCommitType, true, 1);
})
.DisplayToggle(bShowToggle)
.ToggleXChecked(ToggleXChecked)
.ToggleYChecked(ToggleYChecked)
.OnToggleXChanged(OnToggleXChanged)
.OnToggleYChanged(OnToggleYChanged)
.IsEnabled(bIsEnabled)
.PreventThrottling(true)
]
.CopyAction(FUIAction(
FExecuteAction::CreateLambda([ValueType, Keys, Hierarchy]()
{
const FVector3f Data3 = Hierarchy->GetControlValue<FVector3f>(Keys[0], ValueType);
const FVector2f Data(Data3.X, Data3.Y);
FString Content = Data.ToString();
FPlatformApplicationMisc::ClipboardCopy(*Content);
}),
FCanExecuteAction())
)
.PasteAction(FUIAction(
FExecuteAction::CreateLambda([ValueType, Keys, HierarchyToChange]()
{
FString Content;
FPlatformApplicationMisc::ClipboardPaste(Content);
if(Content.IsEmpty())
{
return;
}
FVector2f Data = FVector2f::ZeroVector;
Data.InitFromString(Content);
FVector3f Data3(Data.X, Data.Y, 0);
FScopedTransaction Transaction(NSLOCTEXT("ControlRigElementDetails", "ChangeValue", "Change Value"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<FVector3f>(Data3), ValueType, true, true);
}
}),
FCanExecuteAction::CreateLambda([bIsEnabled]() { return bIsEnabled; }))
);
if((ValueType == ERigControlValueType::Current || ValueType == ERigControlValueType::Initial) && bIsEnabled)
{
WidgetRow.OverrideResetToDefault(FResetToDefaultOverride::Create(
TAttribute<bool>::CreateLambda([ValueType, Keys, Hierarchy]() -> bool
{
const FVector3f FirstValue = Hierarchy->GetControlValue<FVector3f>(Keys[0], ValueType);
const FVector3f ReferenceValue = ValueType == ERigControlValueType::Initial ? FVector3f::ZeroVector :
Hierarchy->GetControlValue<FVector3f>(Keys[0], ERigControlValueType::Initial);
return !(FirstValue - ReferenceValue).IsNearlyZero();
}),
FSimpleDelegate::CreateLambda([ValueType, Keys, HierarchyToChange]()
{
FScopedTransaction Transaction(LOCTEXT("ResetValueToDefault", "Reset Value To Default"));
HierarchyToChange->Modify();
for(const FRigElementKey& Key : Keys)
{
const FVector3f ReferenceValue = ValueType == ERigControlValueType::Initial ? FVector3f::ZeroVector :
HierarchyToChange->GetControlValue<FVector3f>(Keys[0], ERigControlValueType::Initial);
HierarchyToChange->SetControlValue(Key, FRigControlValue::Make<FVector3f>(ReferenceValue), ValueType, true, true);
}
})
));
}
return WidgetRow;
}
void FRigNullElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
FRigTransformElementDetails::CustomizeDetails(DetailBuilder);
CustomizeTransform(DetailBuilder);
CustomizeComponents(DetailBuilder);
CustomizeMetadata(DetailBuilder);
}
void FRigConnectorElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
FRigTransformElementDetails::CustomizeDetails(DetailBuilder);
CustomizeSettings(DetailBuilder);
CustomizeComponents(DetailBuilder);
CustomizeConnectorTargets(DetailBuilder);
CustomizeMetadata(DetailBuilder);
}
void FRigConnectorElementDetails::CustomizeSettings(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
if(IsAnyElementNotOfType(ERigElementType::Connector))
{
return;
}
const TSharedPtr<IPropertyHandle> SettingsHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(FRigConnectorElement, Settings));
DetailBuilder.HideProperty(SettingsHandle);
IDetailCategoryBuilder& SettingsCategory = DetailBuilder.EditCategory(TEXT("Settings"), LOCTEXT("Settings", "Settings"));
ConnectorTypeHandle = SettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRigConnectorSettings, Type));
SettingsCategory
.AddProperty(ConnectorTypeHandle)
.IsEnabled(false);
SettingsCategory
.AddProperty(SettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRigConnectorSettings, bOptional)))
.Visibility(IsAnyConnectorPrimary() ? EVisibility::Collapsed : EVisibility::Visible)
.IsEnabled(!IsAnyConnectorImported());
bool bHideRules = false;
uint32 FirstHash = UINT32_MAX;
for (const FPerElementInfo& Info : PerElementInfos)
{
if (URigHierarchy* Hierarchy = Info.IsValid() ? Info.GetHierarchy() : nullptr)
{
if(const FRigConnectorElement* Connector = Info.GetElement<FRigConnectorElement>())
{
const uint32 Hash = Connector->Settings.GetRulesHash();
if(FirstHash == UINT32_MAX)
{
FirstHash = Hash;
}
else if(FirstHash != Hash)
{
bHideRules = true;
break;
}
}
else
{
bHideRules = true;
}
}
}
IsArrayHandle = SettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRigConnectorSettings, bIsArray));
IDetailPropertyRow& IsArrayPropertyRow = SettingsCategory
.AddProperty(IsArrayHandle)
.IsEnabled(IsArrayEnabled());
if(!IsArrayEnabled())
{
IsArrayPropertyRow.ToolTip(LOCTEXT("PrimaryConnectorsDontAllowArrayToolTip", "Primary Connectors don't support arrays. Add a secondary connector for that."));
}
if(!bHideRules)
{
SettingsCategory
.AddProperty(SettingsHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FRigConnectorSettings, Rules)))
.IsEnabled(!IsAnyConnectorImported());
}
}
void FRigConnectorElementDetails::CustomizeConnectorTargets(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.Num() != 1)
{
return;
}
const bool bArrayEnabled = IsArrayEnabled();
URigHierarchy* Hierarchy = PerElementInfos[0].GetHierarchy();
check(Hierarchy);
FRigTreeDelegates RigTreeDelegates;
RigTreeDelegates.OnGetHierarchy.BindLambda([Hierarchy]()
{
return Hierarchy;
});
TArray<FRigElementKey> CurrentTargets;
if(const UControlRig* ControlRig = Hierarchy->GetTypedOuter<UControlRig>())
{
if(const FRigConnectorElement* Connector = PerElementInfos[0].Element.Get<FRigConnectorElement>())
{
const FRigElementKeyRedirector Redirector = ControlRig->GetElementKeyRedirector();
const FRigElementKeyRedirector::FCachedKeyArray* Cache = Redirector.Find(Connector->GetKey());
if(Cache && !Cache->IsEmpty())
{
for(int32 Index = 0; Index < Cache->Num(); Index++)
{
CurrentTargets.Add((*Cache)[Index].GetKey());
}
}
}
}
IDetailCategoryBuilder& TargetsCategory = DetailBuilder.EditCategory(TEXT("Targets"), LOCTEXT("Targets", "Targets"));
FDetailWidgetRow& TargetsRow = TargetsCategory.AddCustomRow(LOCTEXT("Targets", "Targets"));
TSharedRef<SRigConnectorTargetWidget> ConnectorTargetWidget = SNew(SRigConnectorTargetWidget)
.Outer(PerElementInfos[0].WrapperObject.Get())
.ConnectorKey(PerElementInfos[0].Element.GetKey())
.IsArray(bArrayEnabled)
.ExpandArrayByDefault(true)
.Targets(CurrentTargets)
.OnSetTargetArray(FRigConnectorTargetWidget_SetTargetArray::CreateSP(this, &FRigConnectorElementDetails::OnTargetsChanged))
.RigTreeDelegates(RigTreeDelegates);
if(bArrayEnabled)
{
TargetsRow.WholeRowContent()
[
ConnectorTargetWidget
];
}
else
{
TargetsRow
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Target", "Target"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
ConnectorTargetWidget
];
}
}
TOptional<EConnectorType> FRigConnectorElementDetails::GetConnectorType() const
{
TOptional<EConnectorType> Result;
for (const FPerElementInfo& Info : PerElementInfos)
{
if(const FRigConnectorElement* Connector = Info.GetElement<FRigConnectorElement>())
{
if(!Result.IsSet())
{
Result = Connector->Settings.Type;
}
else if(Result.GetValue() != Connector->Settings.Type)
{
return {};
}
}
}
return Result;
}
TOptional<bool> FRigConnectorElementDetails::GetIsConnectorArray() const
{
TOptional<bool> Result;
for (const FPerElementInfo& Info : PerElementInfos)
{
if(const FRigConnectorElement* Connector = Info.GetElement<FRigConnectorElement>())
{
if(!Result.IsSet())
{
Result = Connector->IsArrayConnector();
}
else if(Result.GetValue() != Connector->IsArrayConnector())
{
return {};
}
}
}
return Result;
}
bool FRigConnectorElementDetails::IsArrayEnabled() const
{
const TOptional<EConnectorType> ConnectorType = GetConnectorType();
if(ConnectorType.Get(EConnectorType::Primary) != EConnectorType::Primary)
{
return !IsAnyConnectorImported();
}
return false;
}
bool FRigConnectorElementDetails::OnTargetsChanged(TArray<FRigElementKey> InTargets)
{
if(PerElementInfos.Num() == 1)
{
if(UControlRigBlueprint* ControlRigBlueprint = PerElementInfos[0].GetBlueprint())
{
return ControlRigBlueprint->ResolveConnectorToArray(PerElementInfos[0].Element.GetKey(), InTargets, true);
}
}
return false;
}
void FRigSocketElementDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
FRigTransformElementDetails::CustomizeDetails(DetailBuilder);
CustomizeSettings(DetailBuilder);
CustomizeTransform(DetailBuilder);
CustomizeComponents(DetailBuilder);
CustomizeMetadata(DetailBuilder);
}
void FRigSocketElementDetails::CustomizeSettings(IDetailLayoutBuilder& DetailBuilder)
{
if(PerElementInfos.IsEmpty())
{
return;
}
if(IsAnyElementNotOfType(ERigElementType::Socket))
{
return;
}
const bool bIsProcedural = IsAnyElementProcedural();
IDetailCategoryBuilder& SettingsCategory = DetailBuilder.EditCategory(TEXT("Settings"), LOCTEXT("Settings", "Settings"));
SettingsCategory.AddCustomRow(FText::FromString(TEXT("Color")))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Color", "Color"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SColorBlock)
.IsEnabled(!bIsProcedural)
//.Size(FVector2D(6.0, 38.0))
.Color(this, &FRigSocketElementDetails::GetSocketColor)
.OnMouseButtonDown(this, &FRigSocketElementDetails::SetSocketColor)
];
SettingsCategory.AddCustomRow(FText::FromString(TEXT("Description")))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Description", "Description"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
[
SNew(SEditableText)
.IsEnabled(!bIsProcedural)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(this, &FRigSocketElementDetails::GetSocketDescription)
.OnTextCommitted(this, &FRigSocketElementDetails::SetSocketDescription)
];
}
FReply FRigSocketElementDetails::SetSocketColor(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
FColorPickerArgs PickerArgs;
PickerArgs.bUseAlpha = false;
PickerArgs.DisplayGamma = TAttribute<float>::Create(TAttribute<float>::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma));
PickerArgs.InitialColor = GetSocketColor();
PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateSP(this, &FRigSocketElementDetails::OnSocketColorPicked);
OpenColorPicker(PickerArgs);
return FReply::Handled();
}
FLinearColor FRigSocketElementDetails::GetSocketColor() const
{
if(PerElementInfos.Num() > 1)
{
return FRigSocketElement::SocketDefaultColor;
}
URigHierarchy* Hierarchy = PerElementInfos[0].GetDefaultHierarchy();
const FRigSocketElement* Socket = PerElementInfos[0].GetDefaultElement<FRigSocketElement>();
return Socket->GetColor(Hierarchy);
}
void FRigSocketElementDetails::OnSocketColorPicked(FLinearColor NewColor)
{
FScopedTransaction Transaction(LOCTEXT("SocketColorChanged", "Socket Color Changed"));
for(FPerElementInfo& Info : PerElementInfos)
{
URigHierarchy* Hierarchy = Info.GetDefaultHierarchy();
Hierarchy->Modify();
FRigSocketElement* Socket = Info.GetDefaultElement<FRigSocketElement>();
Socket->SetColor(NewColor, Hierarchy);
}
}
void FRigSocketElementDetails::SetSocketDescription(const FText& InDescription, ETextCommit::Type InCommitType)
{
const FString Description = InDescription.ToString();
for(FPerElementInfo& Info : PerElementInfos)
{
URigHierarchy* Hierarchy = Info.GetDefaultHierarchy();
Hierarchy->Modify();
FRigSocketElement* Socket = Info.GetDefaultElement<FRigSocketElement>();
Socket->SetDescription(Description, Hierarchy);
}
}
FText FRigSocketElementDetails::GetSocketDescription() const
{
FString FirstValue;
for(int32 Index = 0; Index < PerElementInfos.Num(); Index++)
{
URigHierarchy* Hierarchy = PerElementInfos[Index].GetDefaultHierarchy();
const FRigSocketElement* Socket = PerElementInfos[Index].GetDefaultElement<FRigSocketElement>();
const FString Description = Socket->GetDescription(Hierarchy);
if(Index == 0)
{
FirstValue = Description;
}
else if(!FirstValue.Equals(Description, ESearchCase::CaseSensitive))
{
return ControlRigDetailsMultipleValues;
}
}
return FText::FromString(FirstValue);
}
void FRigConnectionRuleDetails::CustomizeHeader(TSharedRef<IPropertyHandle> InStructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
StructPropertyHandle = InStructPropertyHandle.ToSharedPtr();
PropertyUtilities = StructCustomizationUtils.GetPropertyUtilities();
BlueprintBeingCustomized = nullptr;
EnabledAttribute = false;
RigElementKeyDetails_GetCustomizedInfo(InStructPropertyHandle, BlueprintBeingCustomized);
TArray<UObject*> Objects;
StructPropertyHandle->GetOuterObjects(Objects);
FString FirstObjectValue;
for (int32 Index = 0; Index < Objects.Num(); Index++)
{
FString ObjectValue;
if(InStructPropertyHandle->GetPerObjectValue(Index, ObjectValue) == FPropertyAccess::Result::Success)
{
if(FirstObjectValue.IsEmpty())
{
FirstObjectValue = ObjectValue;
}
else
{
if(!FirstObjectValue.Equals(ObjectValue, ESearchCase::CaseSensitive))
{
FirstObjectValue.Reset();
break;
}
}
}
// only enable editing of the rule if the widget is nested under a wrapper object (a rig element)
if(Objects[Index]->IsA<URigVMDetailsViewWrapperObject>())
{
EnabledAttribute = true;
}
}
if(!FirstObjectValue.IsEmpty())
{
FRigConnectionRuleStash::StaticStruct()->ImportText(*FirstObjectValue, &RuleStash, nullptr, EPropertyPortFlags::PPF_None, nullptr, FRigConnectionRuleStash::StaticStruct()->GetName(), true);
}
if (BlueprintBeingCustomized == nullptr || FirstObjectValue.IsEmpty())
{
HeaderRow
.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
StructPropertyHandle->CreatePropertyValueWidget()
];
}
else
{
HeaderRow
.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SComboButton)
.ContentPadding(FMargin(2,2,2,1))
.ButtonContent()
[
SNew(STextBlock)
.Text(this, &FRigConnectionRuleDetails::OnGetStructTextValue)
]
.OnGetMenuContent(this, &FRigConnectionRuleDetails::GenerateStructPicker)
.IsEnabled(EnabledAttribute)
];
}
}
void FRigConnectionRuleDetails::CustomizeChildren(TSharedRef<IPropertyHandle> InStructPropertyHandle, IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
const UScriptStruct* ScriptStruct = RuleStash.GetScriptStruct();
if(ScriptStruct == nullptr)
{
return;
}
(void)RuleStash.Get(Storage);
const TSharedRef<FStructOnScope> StorageRef = Storage.ToSharedRef();
const FSimpleDelegate OnPropertyChanged = FSimpleDelegate::CreateSP(this, &FRigConnectionRuleDetails::OnRuleContentChanged);
TArray<TSharedPtr<IPropertyHandle>> ChildProperties = InStructPropertyHandle->AddChildStructure(StorageRef);
for (TSharedPtr<IPropertyHandle> ChildHandle : ChildProperties)
{
ChildHandle->SetOnPropertyValueChanged(OnPropertyChanged);
IDetailPropertyRow& ChildRow = StructBuilder.AddProperty(ChildHandle.ToSharedRef());
ChildRow.IsEnabled(EnabledAttribute);
}
}
TSharedRef<SWidget> FRigConnectionRuleDetails::GenerateStructPicker()
{
FStructViewerModule& StructViewerModule = FModuleManager::LoadModuleChecked<FStructViewerModule>("StructViewer");
class FRigConnectionRuleFilter : public IStructViewerFilter
{
public:
FRigConnectionRuleFilter()
{
}
virtual bool IsStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const UScriptStruct* InStruct, TSharedRef<FStructViewerFilterFuncs> InFilterFuncs) override
{
static const UScriptStruct* BaseStruct = FRigConnectionRule::StaticStruct();
return InStruct != BaseStruct && InStruct->IsChildOf(BaseStruct);
}
virtual bool IsUnloadedStructAllowed(const FStructViewerInitializationOptions& InInitOptions, const FSoftObjectPath& InStructPath, TSharedRef<FStructViewerFilterFuncs> InFilterFuncs) override
{
return false;
}
};
static TSharedPtr<FRigConnectionRuleFilter> Filter = MakeShared<FRigConnectionRuleFilter>();
FStructViewerInitializationOptions Options;
{
Options.StructFilter = Filter;
Options.Mode = EStructViewerMode::StructPicker;
Options.DisplayMode = EStructViewerDisplayMode::ListView;
Options.NameTypeToDisplay = EStructViewerNameTypeToDisplay::DisplayName;
Options.bShowNoneOption = false;
Options.bShowUnloadedStructs = false;
Options.bAllowViewOptions = false;
}
return
SNew(SBox)
.WidthOverride(330.0f)
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.FillHeight(1.0f)
.MaxHeight(500)
[
SNew(SBorder)
.Padding(4)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
StructViewerModule.CreateStructViewer(Options, FOnStructPicked::CreateSP(this, &FRigConnectionRuleDetails::OnPickedStruct))
]
]
];
}
void FRigConnectionRuleDetails::OnPickedStruct(const UScriptStruct* ChosenStruct)
{
if(ChosenStruct == nullptr)
{
RuleStash = FRigConnectionRuleStash();
}
else
{
RuleStash.ScriptStructPath = ChosenStruct->GetPathName();
RuleStash.ExportedText = TEXT("()");
Storage.Reset();
RuleStash.Get(Storage);
}
OnRuleContentChanged();
}
FText FRigConnectionRuleDetails::OnGetStructTextValue() const
{
const UScriptStruct* ScriptStruct = RuleStash.GetScriptStruct();
return ScriptStruct
? FText::AsCultureInvariant(ScriptStruct->GetDisplayNameText())
: LOCTEXT("None", "None");
}
void FRigConnectionRuleDetails::OnRuleContentChanged()
{
const UScriptStruct* ScriptStruct = RuleStash.GetScriptStruct();
if(Storage && Storage->GetStruct() == ScriptStruct)
{
RuleStash.ExportedText.Reset();
const uint8* StructMemory = Storage->GetStructMemory();
ScriptStruct->ExportText(RuleStash.ExportedText, StructMemory, StructMemory, nullptr, PPF_None, nullptr);
}
FString Content;
FRigConnectionRuleStash::StaticStruct()->ExportText(Content, &RuleStash, &RuleStash, nullptr, PPF_None, nullptr);
TArray<UObject*> Objects;
StructPropertyHandle->GetOuterObjects(Objects);
FString FirstObjectValue;
for (int32 Index = 0; Index < Objects.Num(); Index++)
{
(void)StructPropertyHandle->SetPerObjectValue(Index, Content, EPropertyValueSetFlags::DefaultFlags);
}
StructPropertyHandle->GetParentHandle()->NotifyPostChange(EPropertyChangeType::ValueSet);
}
void FRigBaseComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
TArray<TWeakObjectPtr<UObject>> DetailObjects;
DetailBuilder.GetObjectsBeingCustomized(DetailObjects);
bool bIsReadOnly = false;
TArray<URigVMDetailsViewWrapperObject*> WrapperObjects;
for(TWeakObjectPtr<UObject> DetailObject : DetailObjects)
{
URigVMDetailsViewWrapperObject* WrapperObject = CastChecked<URigVMDetailsViewWrapperObject>(DetailObject.Get());
if(WrapperObject->GetWrappedStruct()->IsChildOf(FRigBaseComponent::StaticStruct()))
{
WrapperObjects.Add(WrapperObject);
if (const URigHierarchy* Hierarchy = Cast<URigHierarchy>(WrapperObject->GetSubject()))
{
const FRigBaseComponent WrappedComponent = WrapperObject->GetContent<FRigBaseComponent>();
if(const FRigBaseComponent* Component = Hierarchy->FindComponent(WrappedComponent.GetKey()))
{
if(Component->IsProcedural())
{
bIsReadOnly = true;
break;
}
}
}
}
}
if(bIsReadOnly)
{
TArray<FName> CategoryNames;
DetailBuilder.GetCategoryNames(CategoryNames);
for(const FName& CategoryName : CategoryNames)
{
TArray<TSharedRef<IPropertyHandle>> Properties;
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(CategoryName);
Category.GetDefaultProperties(Properties);
for(TSharedRef<IPropertyHandle>& Property : Properties)
{
if(IDetailPropertyRow* Row = DetailBuilder.EditDefaultProperty(Property))
{
Row->IsEnabled(false);
}
}
}
}
}
#undef LOCTEXT_NAMESPACE