You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
"Call function" anim node: - Adds the ability to call functions at different points in the anim graph execution (and under different conditions, e.g. when a branch has started blending in). Only thread-safe functions are allowed to be called. - Adds a thread-safe override point BlueprintThreadSafeUpdateAnimation, called on worker threads for the main instance and for linked instances when they are relevant in the graph (only one call per frame for linked layer instances). Subsystems: - Added new override points pre/post event graph(s) (moved override point for worker thread work to around the thread safe update function call). Improves property access integration: - Property access now shows (and allows the user to override) the call site of accesses. This is to allow users to see when their property access calls will be made, hopefully making its use less confusing for power users. - Tweaked UX for property access nodes and dropdowns. - Anim node pins now have property access bindings in-line on the pin. Also adds the abilility for the anim graph to opt-in (via a config flag) to more stringent thread safety checks. Disabled by default for now as this requires content fixup. #jira UE-115745 - Anim Blueprint Encapsulation #rb Jurre.deBaare [CL 16434092 by Thomas Sarkanen in ue5-main branch]
1239 lines
40 KiB
C++
1239 lines
40 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimGraphNodeDetails.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "BoneContainer.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Animation/AnimationAsset.h"
|
|
#include "DetailWidgetRow.h"
|
|
#include "IDetailPropertyRow.h"
|
|
#include "DetailLayoutBuilder.h"
|
|
#include "DetailCategoryBuilder.h"
|
|
#include "PropertyCustomizationHelpers.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Animation/AnimInstance.h"
|
|
#include "Animation/EditorParentPlayerListObj.h"
|
|
#include "Animation/AnimBlueprintGeneratedClass.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "IDocumentation.h"
|
|
#include "ObjectEditorUtils.h"
|
|
#include "AnimGraphNode_Base.h"
|
|
#include "Widgets/Views/STreeView.h"
|
|
#include "BoneSelectionWidget.h"
|
|
#include "Animation/BlendProfile.h"
|
|
#include "AnimGraphNode_AssetPlayerBase.h"
|
|
#include "ISkeletonEditorModule.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "BlueprintEditor.h"
|
|
#include "Animation/EditorAnimCurveBoneLinks.h"
|
|
#include "IEditableSkeleton.h"
|
|
#include "IDetailChildrenBuilder.h"
|
|
#include "Widgets/Input/SNumericEntryBox.h"
|
|
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
|
|
#include "AnimGraphNode_StateMachine.h"
|
|
#include "AnimGraphNode_SequencePlayer.h"
|
|
#include "Engine/SkeletalMeshSocket.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "LODInfoUILayout.h"
|
|
#include "IPersonaToolkit.h"
|
|
#include "Interfaces/Interface_BoneReferenceSkeletonProvider.h"
|
|
#include "IPropertyAccessEditor.h"
|
|
#include "Algo/Accumulate.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "KismetNodeWithOptionalPinsDetails"
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FAnimGraphNodeDetails
|
|
|
|
TSharedRef<IDetailCustomization> FAnimGraphNodeDetails::MakeInstance()
|
|
{
|
|
return MakeShareable(new FAnimGraphNodeDetails());
|
|
}
|
|
|
|
void FAnimGraphNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder)
|
|
{
|
|
TArray< TWeakObjectPtr<UObject> > SelectedObjectsList;
|
|
DetailBuilder.GetObjectsBeingCustomized(SelectedObjectsList);
|
|
|
|
// Hide the pin options property; it's represented inline per-property instead
|
|
IDetailCategoryBuilder& PinOptionsCategory = DetailBuilder.EditCategory("PinOptions");
|
|
TSharedRef<IPropertyHandle> AvailablePins = DetailBuilder.GetProperty("ShowPinForProperties");
|
|
DetailBuilder.HideProperty(AvailablePins);
|
|
TSharedRef<IPropertyHandle> PropertyBindings = DetailBuilder.GetProperty("PropertyBindings");
|
|
DetailBuilder.HideProperty(PropertyBindings);
|
|
|
|
// get first animgraph nodes
|
|
UAnimGraphNode_Base* AnimGraphNode = Cast<UAnimGraphNode_Base>(SelectedObjectsList[0].Get());
|
|
if (AnimGraphNode == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// make sure type matches with all the nodes.
|
|
const UAnimGraphNode_Base* FirstNodeType = AnimGraphNode;
|
|
for (int32 Index = 1; Index < SelectedObjectsList.Num(); ++Index)
|
|
{
|
|
UAnimGraphNode_Base* CurrentNode = Cast<UAnimGraphNode_Base>(SelectedObjectsList[Index].Get());
|
|
if (!CurrentNode || CurrentNode->GetClass() != FirstNodeType->GetClass())
|
|
{
|
|
// if type mismatches, multi selection doesn't work, just return
|
|
return;
|
|
}
|
|
}
|
|
|
|
TargetSkeleton = AnimGraphNode->GetAnimBlueprint()->TargetSkeleton;
|
|
TargetSkeletonName = TargetSkeleton ? FString::Printf(TEXT("%s'%s'"), *TargetSkeleton->GetClass()->GetName(), *TargetSkeleton->GetPathName()) : FString(TEXT(""));
|
|
|
|
// Get the node property
|
|
const FStructProperty* NodeProperty = AnimGraphNode->GetFNodeProperty();
|
|
if (NodeProperty == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// customize anim graph node's own details if needed
|
|
AnimGraphNode->CustomizeDetails(DetailBuilder);
|
|
|
|
// Hide the Node property as we are going to be adding its inner properties below
|
|
TSharedRef<IPropertyHandle> NodePropertyHandle = DetailBuilder.GetProperty(NodeProperty->GetFName(), AnimGraphNode->GetClass());
|
|
DetailBuilder.HideProperty(NodePropertyHandle);
|
|
|
|
uint32 NumChildHandles = 0;
|
|
FPropertyAccess::Result Result = NodePropertyHandle->GetNumChildren(NumChildHandles);
|
|
if (Result != FPropertyAccess::Fail)
|
|
{
|
|
for (uint32 ChildHandleIndex = 0; ChildHandleIndex < NumChildHandles; ++ChildHandleIndex)
|
|
{
|
|
TSharedPtr<IPropertyHandle> TargetPropertyHandle = NodePropertyHandle->GetChildHandle(ChildHandleIndex);
|
|
if (TargetPropertyHandle.IsValid())
|
|
{
|
|
FProperty* TargetProperty = TargetPropertyHandle->GetProperty();
|
|
IDetailCategoryBuilder& CurrentCategory = DetailBuilder.EditCategory(FObjectEditorUtils::GetCategoryFName(TargetProperty));
|
|
|
|
int32 CustomPinIndex = AnimGraphNode->ShowPinForProperties.IndexOfByPredicate([TargetProperty](const FOptionalPinFromProperty& InOptionalPin)
|
|
{
|
|
return TargetProperty->GetFName() == InOptionalPin.PropertyName;
|
|
});
|
|
|
|
if (CustomPinIndex != INDEX_NONE)
|
|
{
|
|
const FOptionalPinFromProperty& OptionalPin = AnimGraphNode->ShowPinForProperties[CustomPinIndex];
|
|
|
|
// Not optional
|
|
if (!OptionalPin.bCanToggleVisibility && OptionalPin.bShowPin)
|
|
{
|
|
// Always displayed as a pin, so hide the property
|
|
DetailBuilder.HideProperty(TargetPropertyHandle);
|
|
continue;
|
|
}
|
|
|
|
if (!TargetPropertyHandle->GetProperty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if customized, do not do anything
|
|
if (TargetPropertyHandle->IsCustomized())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// sometimes because of order of customization
|
|
// this gets called first for the node you'd like to customize
|
|
// then the above statement won't work
|
|
// so you can mark certain property to have meta data "CustomizeProperty"
|
|
// which will trigger below statement
|
|
if (OptionalPin.bPropertyIsCustomized)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<SWidget> InternalCustomWidget = CreatePropertyWidget(TargetProperty, TargetPropertyHandle.ToSharedRef(), AnimGraphNode->GetClass());
|
|
|
|
if (OptionalPin.bCanToggleVisibility)
|
|
{
|
|
IDetailPropertyRow& PropertyRow = CurrentCategory.AddProperty(TargetPropertyHandle);
|
|
|
|
TSharedPtr<SWidget> NameWidget;
|
|
TSharedPtr<SWidget> ValueWidget;
|
|
FDetailWidgetRow Row;
|
|
PropertyRow.GetDefaultWidgets(NameWidget, ValueWidget, Row);
|
|
|
|
ValueWidget = (InternalCustomWidget == SNullWidget::NullWidget) ? ValueWidget : InternalCustomWidget;
|
|
|
|
const FName OptionalPinArrayEntryName(*FString::Printf(TEXT("ShowPinForProperties[%d].bShowPin"), CustomPinIndex));
|
|
TSharedRef<IPropertyHandle> ShowHidePropertyHandle = DetailBuilder.GetProperty(OptionalPinArrayEntryName);
|
|
|
|
ShowHidePropertyHandle->MarkHiddenByCustomization();
|
|
|
|
ValueWidget->SetVisibility(TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FAnimGraphNodeDetails::GetVisibilityOfProperty, ShowHidePropertyHandle)));
|
|
|
|
// If we have an edit condition, that comes as part of the default name widget, so just use a text block to avoid duplicate checkboxes
|
|
TSharedPtr<SWidget> PropertyNameWidget;
|
|
if (TargetProperty->HasMetaData(TEXT("EditCondition")))
|
|
{
|
|
PropertyNameWidget = SNew(STextBlock)
|
|
.Text(TargetProperty->GetDisplayNameText())
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ToolTipText(TargetProperty->GetToolTipText());
|
|
}
|
|
else
|
|
{
|
|
PropertyNameWidget = NameWidget;
|
|
}
|
|
|
|
NameWidget = PropertyNameWidget;
|
|
|
|
// we only show children if visibility is one
|
|
// whenever toggles, this gets called, so it will be refreshed
|
|
const bool bShowChildren = GetVisibilityOfProperty(ShowHidePropertyHandle) == EVisibility::Visible;
|
|
PropertyRow.CustomWidget(bShowChildren)
|
|
.NameContent()
|
|
.MinDesiredWidth(Row.NameWidget.MinWidth)
|
|
.MaxDesiredWidth(Row.NameWidget.MaxWidth)
|
|
[
|
|
NameWidget.ToSharedRef()
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(Row.ValueWidget.MinWidth)
|
|
.MaxDesiredWidth(Row.ValueWidget.MaxWidth)
|
|
[
|
|
ValueWidget.ToSharedRef()
|
|
];
|
|
}
|
|
else if (InternalCustomWidget != SNullWidget::NullWidget)
|
|
{
|
|
// A few properties are internally customized within this customization. Here we
|
|
// catch instances of these that don't have an optional pin flag.
|
|
IDetailPropertyRow& PropertyRow = CurrentCategory.AddProperty(TargetPropertyHandle);
|
|
PropertyRow.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
TargetPropertyHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
[
|
|
InternalCustomWidget
|
|
];
|
|
}
|
|
else
|
|
{
|
|
CurrentCategory.AddProperty(TargetPropertyHandle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TSharedRef<SWidget> FAnimGraphNodeDetails::CreatePropertyWidget(FProperty* TargetProperty, TSharedRef<IPropertyHandle> TargetPropertyHandle, UClass* NodeClass)
|
|
{
|
|
if(const FObjectPropertyBase* ObjectProperty = CastField<const FObjectPropertyBase>( TargetProperty ))
|
|
{
|
|
if(ObjectProperty->PropertyClass->IsChildOf(UAnimationAsset::StaticClass()))
|
|
{
|
|
bool bAllowClear = !(ObjectProperty->PropertyFlags & CPF_NoClear);
|
|
|
|
return SNew(SObjectPropertyEntryBox)
|
|
.PropertyHandle(TargetPropertyHandle)
|
|
.AllowedClass(ObjectProperty->PropertyClass)
|
|
.AllowClear(bAllowClear)
|
|
.OnShouldFilterAsset(FOnShouldFilterAsset::CreateSP(this, &FAnimGraphNodeDetails::OnShouldFilterAnimAsset, NodeClass));
|
|
}
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
bool FAnimGraphNodeDetails::OnShouldFilterAnimAsset( const FAssetData& AssetData, UClass* NodeToFilterFor ) const
|
|
{
|
|
FAssetDataTagMapSharedView::FFindTagResult Result = AssetData.TagsAndValues.FindTag("Skeleton");
|
|
if (Result.IsSet() && TargetSkeleton && TargetSkeleton->IsCompatibleSkeletonByAssetData(AssetData))
|
|
{
|
|
const UClass* AssetClass = AssetData.GetClass();
|
|
// If node is an 'asset player', only let you select the right kind of asset for it
|
|
if (!NodeToFilterFor->IsChildOf(UAnimGraphNode_AssetPlayerBase::StaticClass()) || SupportNodeClassForAsset(AssetClass, NodeToFilterFor))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
EVisibility FAnimGraphNodeDetails::GetVisibilityOfProperty(TSharedRef<IPropertyHandle> Handle) const
|
|
{
|
|
bool bShowAsPin;
|
|
if (FPropertyAccess::Success == Handle->GetValue(/*out*/ bShowAsPin))
|
|
{
|
|
return bShowAsPin ? EVisibility::Hidden : EVisibility::Visible;
|
|
}
|
|
else
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
}
|
|
|
|
void FAnimGraphNodeDetails::OnBlendProfileChanged(UBlendProfile* NewProfile, TSharedPtr<IPropertyHandle> PropertyHandle)
|
|
{
|
|
if(PropertyHandle.IsValid())
|
|
{
|
|
PropertyHandle->SetValue(NewProfile);
|
|
}
|
|
}
|
|
|
|
|
|
TSharedRef<IPropertyTypeCustomization> FInputScaleBiasCustomization::MakeInstance()
|
|
{
|
|
return MakeShareable(new FInputScaleBiasCustomization());
|
|
}
|
|
|
|
float GetMinValue(float Scale, float Bias)
|
|
{
|
|
return Scale != 0.0f ? (FMath::Abs(Bias) < SMALL_NUMBER ? 0.0f : -Bias) / Scale : 0.0f; // to avoid displaying of - in front of 0
|
|
}
|
|
|
|
float GetMaxValue(float Scale, float Bias)
|
|
{
|
|
return Scale != 0.0f ? (1.0f - Bias) / Scale : 0.0f;
|
|
}
|
|
|
|
void UpdateInputScaleBiasWithMinValue(float MinValue, TSharedRef<class IPropertyHandle> InputBiasScaleStructPropertyHandle)
|
|
{
|
|
InputBiasScaleStructPropertyHandle->NotifyPreChange();
|
|
|
|
TSharedRef<class IPropertyHandle> BiasProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Bias").ToSharedRef();
|
|
TSharedRef<class IPropertyHandle> ScaleProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Scale").ToSharedRef();
|
|
TArray<void*> BiasDataArray;
|
|
TArray<void*> ScaleDataArray;
|
|
BiasProperty->AccessRawData(BiasDataArray);
|
|
ScaleProperty->AccessRawData(ScaleDataArray);
|
|
check(BiasDataArray.Num() == ScaleDataArray.Num());
|
|
for(int32 DataIndex = 0; DataIndex < BiasDataArray.Num(); ++DataIndex)
|
|
{
|
|
float* BiasPtr = (float*)BiasDataArray[DataIndex];
|
|
float* ScalePtr = (float*)ScaleDataArray[DataIndex];
|
|
check(BiasPtr);
|
|
check(ScalePtr);
|
|
|
|
const float MaxValue = GetMaxValue(*ScalePtr, *BiasPtr);
|
|
const float Difference = MaxValue - MinValue;
|
|
*ScalePtr = Difference != 0.0f? 1.0f / Difference : 0.0f;
|
|
*BiasPtr = -MinValue * *ScalePtr;
|
|
}
|
|
|
|
InputBiasScaleStructPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
}
|
|
|
|
void UpdateInputScaleBiasWithMaxValue(float MaxValue, TSharedRef<class IPropertyHandle> InputBiasScaleStructPropertyHandle)
|
|
{
|
|
InputBiasScaleStructPropertyHandle->NotifyPreChange();
|
|
|
|
TSharedRef<class IPropertyHandle> BiasProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Bias").ToSharedRef();
|
|
TSharedRef<class IPropertyHandle> ScaleProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Scale").ToSharedRef();
|
|
TArray<void*> BiasDataArray;
|
|
TArray<void*> ScaleDataArray;
|
|
BiasProperty->AccessRawData(BiasDataArray);
|
|
ScaleProperty->AccessRawData(ScaleDataArray);
|
|
check(BiasDataArray.Num() == ScaleDataArray.Num());
|
|
for(int32 DataIndex = 0; DataIndex < BiasDataArray.Num(); ++DataIndex)
|
|
{
|
|
float* BiasPtr = (float*)BiasDataArray[DataIndex];
|
|
float* ScalePtr = (float*)ScaleDataArray[DataIndex];
|
|
check(BiasPtr);
|
|
check(ScalePtr);
|
|
|
|
const float MinValue = GetMinValue(*ScalePtr, *BiasPtr);
|
|
const float Difference = MaxValue - MinValue;
|
|
*ScalePtr = Difference != 0.0f ? 1.0f / Difference : 0.0f;
|
|
*BiasPtr = -MinValue * *ScalePtr;
|
|
}
|
|
|
|
InputBiasScaleStructPropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
}
|
|
|
|
TOptional<float> GetMinValueInputScaleBias(TSharedRef<class IPropertyHandle> InputBiasScaleStructPropertyHandle)
|
|
{
|
|
TSharedRef<class IPropertyHandle> BiasProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Bias").ToSharedRef();
|
|
TSharedRef<class IPropertyHandle> ScaleProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Scale").ToSharedRef();
|
|
float Scale = 1.0f;
|
|
float Bias = 0.0f;
|
|
if(ScaleProperty->GetValue(Scale) == FPropertyAccess::Success && BiasProperty->GetValue(Bias) == FPropertyAccess::Success)
|
|
{
|
|
return GetMinValue(Scale, Bias);
|
|
}
|
|
|
|
return TOptional<float>();
|
|
}
|
|
|
|
TOptional<float> GetMaxValueInputScaleBias(TSharedRef<class IPropertyHandle> InputBiasScaleStructPropertyHandle)
|
|
{
|
|
TSharedRef<class IPropertyHandle> BiasProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Bias").ToSharedRef();
|
|
TSharedRef<class IPropertyHandle> ScaleProperty = InputBiasScaleStructPropertyHandle->GetChildHandle("Scale").ToSharedRef();
|
|
float Scale = 1.0f;
|
|
float Bias = 0.0f;
|
|
if(ScaleProperty->GetValue(Scale) == FPropertyAccess::Success && BiasProperty->GetValue(Bias) == FPropertyAccess::Success)
|
|
{
|
|
return GetMaxValue(Scale, Bias);
|
|
}
|
|
|
|
return TOptional<float>();
|
|
}
|
|
|
|
|
|
void FInputScaleBiasCustomization::CustomizeHeader(TSharedRef<class IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
{
|
|
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void FInputScaleBiasCustomization::CustomizeChildren( TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils )
|
|
{
|
|
TWeakPtr<IPropertyHandle> WeakStructPropertyHandle = StructPropertyHandle;
|
|
|
|
StructBuilder
|
|
.AddProperty(StructPropertyHandle)
|
|
.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
StructPropertyHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(250.0f)
|
|
.MaxDesiredWidth(250.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 2.0f, 3.0f, 2.0f))
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ToolTipText(LOCTEXT("MinInputScaleBias", "Minimum input value"))
|
|
.AllowSpin(true)
|
|
.MinSliderValue(0.0f)
|
|
.MaxSliderValue(2.0f)
|
|
.Value_Lambda([WeakStructPropertyHandle]()
|
|
{
|
|
return GetMinValueInputScaleBias(WeakStructPropertyHandle.Pin().ToSharedRef());
|
|
})
|
|
.OnValueChanged_Lambda([WeakStructPropertyHandle](float InValue)
|
|
{
|
|
UpdateInputScaleBiasWithMinValue(InValue, WeakStructPropertyHandle.Pin().ToSharedRef());
|
|
})
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 2.0f, 0.0f, 2.0f))
|
|
[
|
|
SNew(SNumericEntryBox<float>)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ToolTipText(LOCTEXT("MaxInputScaleBias", "Maximum input value"))
|
|
.AllowSpin(true)
|
|
.MinSliderValue(0.0f)
|
|
.MaxSliderValue(2.0f)
|
|
.Value_Lambda([WeakStructPropertyHandle]()
|
|
{
|
|
return GetMaxValueInputScaleBias(WeakStructPropertyHandle.Pin().ToSharedRef());
|
|
})
|
|
.OnValueChanged_Lambda([WeakStructPropertyHandle](float InValue)
|
|
{
|
|
UpdateInputScaleBiasWithMaxValue(InValue, WeakStructPropertyHandle.Pin().ToSharedRef());
|
|
})
|
|
]
|
|
];
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FBoneReferenceCustomization
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<IPropertyTypeCustomization> FBoneReferenceCustomization::MakeInstance()
|
|
{
|
|
return MakeShareable(new FBoneReferenceCustomization());
|
|
}
|
|
|
|
void FBoneReferenceCustomization::CustomizeHeader( TSharedRef<IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils )
|
|
{
|
|
// set property handle
|
|
SetPropertyHandle(StructPropertyHandle);
|
|
// set editable skeleton info from struct
|
|
SetEditableSkeleton(StructPropertyHandle);
|
|
if (TargetEditableSkeleton.IsValid() && BoneNameProperty->IsValidHandle())
|
|
{
|
|
HeaderRow
|
|
.NameContent()
|
|
[
|
|
StructPropertyHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
.MaxDesiredWidth(0.0f)
|
|
[
|
|
SNew(SBoneSelectionWidget)
|
|
.ToolTipText(StructPropertyHandle->GetToolTipText())
|
|
.OnBoneSelectionChanged(this, &FBoneReferenceCustomization::OnBoneSelectionChanged)
|
|
.OnGetSelectedBone(this, &FBoneReferenceCustomization::GetSelectedBone)
|
|
.OnGetReferenceSkeleton(this, &FBoneReferenceCustomization::GetReferenceSkeleton)
|
|
];
|
|
}
|
|
else
|
|
{
|
|
// if this FBoneReference is used by some other Outers, this will fail
|
|
// should warn programmers instead of silent fail
|
|
ensureAlways(!bEnsureOnInvalidSkeleton);
|
|
UE_LOG(LogAnimation, Warning, TEXT("FBoneReferenceCustomization::CustomizeHeader: SetEditableSkeleton failed to find an appropriate skeleton!"));
|
|
}
|
|
}
|
|
|
|
void FBoneReferenceCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
{
|
|
|
|
}
|
|
void FBoneReferenceCustomization::SetEditableSkeleton(TSharedRef<IPropertyHandle> StructPropertyHandle)
|
|
{
|
|
TArray<UObject*> Objects;
|
|
StructPropertyHandle->GetOuterObjects(Objects);
|
|
|
|
USkeleton* TargetSkeleton = nullptr;
|
|
TSharedPtr<IEditableSkeleton> EditableSkeleton;
|
|
|
|
bEnsureOnInvalidSkeleton = true;
|
|
|
|
auto FindSkeletonForObject = [this, &TargetSkeleton, &EditableSkeleton](UObject* InObject)
|
|
{
|
|
for( ; InObject; InObject = InObject->GetOuter())
|
|
{
|
|
if (UAnimGraphNode_Base* AnimGraphNode = Cast<UAnimGraphNode_Base>(InObject))
|
|
{
|
|
TargetSkeleton = AnimGraphNode->GetAnimBlueprint()->TargetSkeleton;
|
|
break;
|
|
}
|
|
|
|
if (USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(InObject))
|
|
{
|
|
TargetSkeleton = SkeletalMesh->GetSkeleton();
|
|
break;
|
|
}
|
|
|
|
if (ULODInfoUILayout* LODInfoUILayout = Cast<ULODInfoUILayout>(InObject))
|
|
{
|
|
USkeletalMesh* SkeletalMesh = LODInfoUILayout->GetPersonaToolkit()->GetPreviewMesh();
|
|
check(SkeletalMesh);
|
|
TargetSkeleton = SkeletalMesh->GetSkeleton();
|
|
break;
|
|
}
|
|
|
|
if (UAnimationAsset* AnimationAsset = Cast<UAnimationAsset>(InObject))
|
|
{
|
|
if(AnimationAsset->IsAsset())
|
|
{
|
|
TargetSkeleton = AnimationAsset->GetSkeleton();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (UAnimInstance* AnimInstance = Cast<UAnimInstance>(InObject))
|
|
{
|
|
if (AnimInstance->CurrentSkeleton)
|
|
{
|
|
TargetSkeleton = AnimInstance->CurrentSkeleton;
|
|
break;
|
|
}
|
|
else if (UAnimBlueprintGeneratedClass* AnimBPClass = Cast<UAnimBlueprintGeneratedClass>(AnimInstance->GetClass()))
|
|
{
|
|
TargetSkeleton = AnimBPClass->TargetSkeleton;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// editor animation curve bone links are responsible for linking joints to curve
|
|
// this is editor object that only exists for editor
|
|
if (UEditorAnimCurveBoneLinks* AnimCurveObj = Cast<UEditorAnimCurveBoneLinks>(InObject))
|
|
{
|
|
EditableSkeleton = AnimCurveObj->EditableSkeleton.Pin();
|
|
break;
|
|
}
|
|
|
|
if (IBoneReferenceSkeletonProvider* SkeletonProvider = Cast<IBoneReferenceSkeletonProvider>(InObject))
|
|
{
|
|
TargetSkeleton = SkeletonProvider->GetSkeleton(bEnsureOnInvalidSkeleton);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TargetSkeleton != nullptr || EditableSkeleton != nullptr;
|
|
};
|
|
|
|
for (UObject* Object : Objects)
|
|
{
|
|
if(FindSkeletonForObject(Object))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TargetSkeleton != nullptr)
|
|
{
|
|
ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::LoadModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
|
|
EditableSkeleton = SkeletonEditorModule.CreateEditableSkeleton(TargetSkeleton);
|
|
}
|
|
|
|
TargetEditableSkeleton = EditableSkeleton;
|
|
}
|
|
|
|
TSharedPtr<IPropertyHandle> FBoneReferenceCustomization::FindStructMemberProperty(TSharedRef<IPropertyHandle> PropertyHandle, const FName& PropertyName)
|
|
{
|
|
uint32 NumChildren = 0;
|
|
PropertyHandle->GetNumChildren(NumChildren);
|
|
for (uint32 ChildIdx = 0; ChildIdx < NumChildren; ++ChildIdx)
|
|
{
|
|
TSharedPtr<IPropertyHandle> ChildHandle = PropertyHandle->GetChildHandle(ChildIdx);
|
|
if (ChildHandle->GetProperty()->GetFName() == PropertyName)
|
|
{
|
|
return ChildHandle;
|
|
}
|
|
}
|
|
|
|
return TSharedPtr<IPropertyHandle>();
|
|
}
|
|
|
|
void FBoneReferenceCustomization::SetPropertyHandle(TSharedRef<IPropertyHandle> StructPropertyHandle)
|
|
{
|
|
BoneNameProperty = FindStructMemberProperty(StructPropertyHandle, GET_MEMBER_NAME_CHECKED(FBoneReference, BoneName));
|
|
check(BoneNameProperty->IsValidHandle());
|
|
}
|
|
|
|
void FBoneReferenceCustomization::OnBoneSelectionChanged(FName Name)
|
|
{
|
|
BoneNameProperty->SetValue(Name);
|
|
}
|
|
|
|
FName FBoneReferenceCustomization::GetSelectedBone(bool& bMultipleValues) const
|
|
{
|
|
FString OutText;
|
|
|
|
FPropertyAccess::Result Result = BoneNameProperty->GetValueAsFormattedString(OutText);
|
|
bMultipleValues = (Result == FPropertyAccess::MultipleValues);
|
|
|
|
return FName(*OutText);
|
|
}
|
|
|
|
const struct FReferenceSkeleton& FBoneReferenceCustomization::GetReferenceSkeleton() const
|
|
{
|
|
// retruning dummy skeleton if any reason, it is invalid
|
|
static FReferenceSkeleton DummySkeleton;
|
|
|
|
return (TargetEditableSkeleton.IsValid()) ? TargetEditableSkeleton.Get()->GetSkeleton().GetReferenceSkeleton() : DummySkeleton;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
// FBoneSocketTargetCustomization
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
TSharedRef<IPropertyTypeCustomization> FBoneSocketTargetCustomization::MakeInstance()
|
|
{
|
|
return MakeShareable(new FBoneSocketTargetCustomization());
|
|
}
|
|
|
|
void FBoneSocketTargetCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
{
|
|
// set property handle
|
|
SetPropertyHandle(StructPropertyHandle);
|
|
// set editable skeleton info from struct
|
|
SetEditableSkeleton(StructPropertyHandle);
|
|
Build(StructPropertyHandle, ChildBuilder);
|
|
}
|
|
|
|
void FBoneSocketTargetCustomization::SetPropertyHandle(TSharedRef<IPropertyHandle> StructPropertyHandle)
|
|
{
|
|
TSharedPtr<IPropertyHandle> BoneReferenceProperty = FindStructMemberProperty(StructPropertyHandle, GET_MEMBER_NAME_CHECKED(FBoneSocketTarget, BoneReference));
|
|
check(BoneReferenceProperty->IsValidHandle());
|
|
BoneNameProperty = FindStructMemberProperty(BoneReferenceProperty.ToSharedRef(), GET_MEMBER_NAME_CHECKED(FBoneReference, BoneName));
|
|
TSharedPtr<IPropertyHandle> SocketReferenceProperty = FindStructMemberProperty(StructPropertyHandle, GET_MEMBER_NAME_CHECKED(FBoneSocketTarget, SocketReference));
|
|
check(SocketReferenceProperty->IsValidHandle());
|
|
SocketNameProperty = FindStructMemberProperty(SocketReferenceProperty.ToSharedRef(), GET_MEMBER_NAME_CHECKED(FSocketReference, SocketName));
|
|
UseSocketProperty = FindStructMemberProperty(StructPropertyHandle, GET_MEMBER_NAME_CHECKED(FBoneSocketTarget, bUseSocket));
|
|
|
|
check(BoneNameProperty->IsValidHandle() && SocketNameProperty->IsValidHandle() && UseSocketProperty->IsValidHandle());
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
void FBoneSocketTargetCustomization::Build(TSharedRef<IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& ChildBuilder)
|
|
{
|
|
if (TargetEditableSkeleton.IsValid() && BoneNameProperty->IsValidHandle())
|
|
{
|
|
ChildBuilder
|
|
.AddProperty(StructPropertyHandle)
|
|
.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
StructPropertyHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SBoneSelectionWidget)
|
|
.ToolTipText(StructPropertyHandle->GetToolTipText())
|
|
.bShowSocket(true)
|
|
.OnBoneSelectionChanged(this, &FBoneSocketTargetCustomization::OnBoneSelectionChanged)
|
|
.OnGetSelectedBone(this, &FBoneSocketTargetCustomization::GetSelectedBone)
|
|
.OnGetReferenceSkeleton(this, &FBoneReferenceCustomization::GetReferenceSkeleton)
|
|
.OnGetSocketList(this, &FBoneSocketTargetCustomization::GetSocketList)
|
|
];
|
|
}
|
|
else
|
|
{
|
|
// if this FBoneSocketTarget is used by some other Outers, this will fail
|
|
// should warn programmers instead of silent fail
|
|
ensureAlways(false);
|
|
}
|
|
}
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
TSharedPtr<IPropertyHandle> FBoneSocketTargetCustomization::GetNameProperty() const
|
|
{
|
|
bool bUseSocket = false;
|
|
if (UseSocketProperty->GetValue(bUseSocket) == FPropertyAccess::Success)
|
|
{
|
|
if (bUseSocket)
|
|
{
|
|
return SocketNameProperty;
|
|
}
|
|
|
|
return BoneNameProperty;
|
|
}
|
|
|
|
return TSharedPtr<IPropertyHandle>();
|
|
}
|
|
void FBoneSocketTargetCustomization::OnBoneSelectionChanged(FName Name)
|
|
{
|
|
// figure out if the name is BoneName or socket name
|
|
if (TargetEditableSkeleton.IsValid())
|
|
{
|
|
bool bUseSocket = false;
|
|
if (GetReferenceSkeleton().FindBoneIndex(Name) == INDEX_NONE)
|
|
{
|
|
// make sure socket exists
|
|
const TArray<class USkeletalMeshSocket*>& Sockets = GetSocketList();
|
|
for (int32 Idx = 0; Idx < Sockets.Num(); ++Idx)
|
|
{
|
|
if (Sockets[Idx]->SocketName == Name)
|
|
{
|
|
bUseSocket = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// we should find one
|
|
ensure(bUseSocket);
|
|
}
|
|
|
|
// set correct value
|
|
UseSocketProperty->SetValue(bUseSocket);
|
|
|
|
TSharedPtr<IPropertyHandle> NameProperty = GetNameProperty();
|
|
if (ensureAlways(NameProperty.IsValid()))
|
|
{
|
|
NameProperty->SetValue(Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
FName FBoneSocketTargetCustomization::GetSelectedBone(bool& bMultipleValues) const
|
|
{
|
|
FString OutText;
|
|
|
|
TSharedPtr<IPropertyHandle> NameProperty = GetNameProperty();
|
|
if (NameProperty.IsValid())
|
|
{
|
|
FPropertyAccess::Result Result = NameProperty->GetValueAsFormattedString(OutText);
|
|
bMultipleValues = (Result == FPropertyAccess::MultipleValues);
|
|
}
|
|
else
|
|
{
|
|
// there is no single value
|
|
bMultipleValues = true;
|
|
return NAME_None;
|
|
}
|
|
|
|
return FName(*OutText);
|
|
}
|
|
|
|
const TArray<class USkeletalMeshSocket*>& FBoneSocketTargetCustomization::GetSocketList() const
|
|
{
|
|
if (TargetEditableSkeleton.IsValid())
|
|
{
|
|
return TargetEditableSkeleton.Get()->GetSkeleton().Sockets;
|
|
}
|
|
|
|
static TArray<class USkeletalMeshSocket*> DummyList;
|
|
return DummyList;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TSharedRef<IDetailCustomization> FAnimGraphParentPlayerDetails::MakeInstance(TSharedRef<FBlueprintEditor> InBlueprintEditor)
|
|
{
|
|
return MakeShareable(new FAnimGraphParentPlayerDetails(InBlueprintEditor));
|
|
}
|
|
|
|
|
|
void FAnimGraphParentPlayerDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder)
|
|
{
|
|
TArray<TWeakObjectPtr<UObject>> SelectedObjects;
|
|
DetailBuilder.GetObjectsBeingCustomized(SelectedObjects);
|
|
check(SelectedObjects.Num() == 1);
|
|
|
|
EditorObject = Cast<UEditorParentPlayerListObj>(SelectedObjects[0].Get());
|
|
check(EditorObject);
|
|
|
|
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("AnimGraphOverrides");
|
|
DetailBuilder.HideProperty("Overrides");
|
|
|
|
struct FObjectToEntryBuilder
|
|
{
|
|
private:
|
|
TMap<UObject*, TSharedPtr<FPlayerTreeViewEntry>> ObjectToEntryMap;
|
|
TArray<TSharedPtr<FPlayerTreeViewEntry>>& ListEntries;
|
|
|
|
private:
|
|
TSharedPtr<FPlayerTreeViewEntry> AddObject(UObject* Object)
|
|
{
|
|
TSharedPtr<FPlayerTreeViewEntry> Result = ObjectToEntryMap.FindRef(Object);
|
|
if (!Result.IsValid() && (Object != nullptr))
|
|
{
|
|
bool bTopLevel = false;
|
|
TSharedPtr<FPlayerTreeViewEntry> ThisNode;
|
|
|
|
if (UBlueprint* Blueprint = Cast<UBlueprint>(Object))
|
|
{
|
|
ThisNode = MakeShareable(new FPlayerTreeViewEntry(Blueprint->GetName(), EPlayerTreeViewEntryType::Blueprint));
|
|
bTopLevel = true;
|
|
}
|
|
else if (UAnimGraphNode_StateMachine* StateMachineNode = Cast<UAnimGraphNode_StateMachine>(Object))
|
|
{
|
|
// Don't create a node for these, the graph speaks for it
|
|
}
|
|
else if (UAnimGraphNode_AssetPlayerBase * AssetPlayerBase = Cast<UAnimGraphNode_AssetPlayerBase>(Object))
|
|
{
|
|
FString Title = AssetPlayerBase->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
|
ThisNode = MakeShareable(new FPlayerTreeViewEntry(Title, EPlayerTreeViewEntryType::Node));
|
|
}
|
|
else if (UAnimGraphNode_Base* Node = Cast<UAnimGraphNode_Base>(Object))
|
|
{
|
|
ThisNode = MakeShareable(new FPlayerTreeViewEntry(Node->GetName(), EPlayerTreeViewEntryType::Node));
|
|
}
|
|
else if (UEdGraph* Graph = Cast<UEdGraph>(Object))
|
|
{
|
|
ThisNode = MakeShareable(new FPlayerTreeViewEntry(Graph->GetName(), EPlayerTreeViewEntryType::Graph));
|
|
}
|
|
|
|
if (ThisNode.IsValid())
|
|
{
|
|
ObjectToEntryMap.Add(Object, ThisNode);
|
|
}
|
|
|
|
if (bTopLevel)
|
|
{
|
|
ListEntries.Add(ThisNode);
|
|
Result = ThisNode;
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FPlayerTreeViewEntry> Outer = AddObject(Object->GetOuter());
|
|
Result = Outer;
|
|
|
|
if (ThisNode.IsValid())
|
|
{
|
|
Result = ThisNode;
|
|
check(Outer.IsValid())
|
|
Outer->Children.Add(Result);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void SortInternal(TArray<TSharedPtr<FPlayerTreeViewEntry>>& ListToSort)
|
|
{
|
|
ListToSort.Sort([](TSharedPtr<FPlayerTreeViewEntry> A, TSharedPtr<FPlayerTreeViewEntry> B) { return A->EntryName < B->EntryName; });
|
|
|
|
for (TSharedPtr<FPlayerTreeViewEntry>& Entry : ListToSort)
|
|
{
|
|
SortInternal(Entry->Children);
|
|
}
|
|
}
|
|
|
|
public:
|
|
FObjectToEntryBuilder(TArray<TSharedPtr<FPlayerTreeViewEntry>>& InListEntries)
|
|
: ListEntries(InListEntries)
|
|
{
|
|
}
|
|
|
|
void AddNode(UAnimGraphNode_Base* Node, FAnimParentNodeAssetOverride& Override)
|
|
{
|
|
TSharedPtr<FPlayerTreeViewEntry> Result = AddObject(Node);
|
|
if (Result.IsValid())
|
|
{
|
|
Result->Override = &Override;
|
|
}
|
|
}
|
|
|
|
void Sort()
|
|
{
|
|
SortInternal(ListEntries);
|
|
}
|
|
};
|
|
|
|
FObjectToEntryBuilder EntryBuilder(ListEntries);
|
|
|
|
// Build a hierarchy of entires for a tree view in the form of Blueprint->Graph->Node
|
|
for (FAnimParentNodeAssetOverride& Override : EditorObject->Overrides)
|
|
{
|
|
UAnimGraphNode_Base* Node = EditorObject->GetVisualNodeFromGuid(Override.ParentNodeGuid);
|
|
EntryBuilder.AddNode(Node, Override);
|
|
}
|
|
|
|
// Sort the nodes
|
|
EntryBuilder.Sort();
|
|
|
|
FDetailWidgetRow& Row = Category.AddCustomRow(FText::GetEmpty());
|
|
TSharedRef<STreeView<TSharedPtr<FPlayerTreeViewEntry>>> TreeView = SNew(STreeView<TSharedPtr<FPlayerTreeViewEntry>>)
|
|
.SelectionMode(ESelectionMode::None)
|
|
.OnGenerateRow(this, &FAnimGraphParentPlayerDetails::OnGenerateRow)
|
|
.OnGetChildren(this, &FAnimGraphParentPlayerDetails::OnGetChildren)
|
|
.TreeItemsSource(&ListEntries)
|
|
.HeaderRow
|
|
(
|
|
SNew(SHeaderRow)
|
|
+SHeaderRow::Column(FName("Name"))
|
|
.FillWidth(0.5f)
|
|
.DefaultLabel(LOCTEXT("ParentPlayer_NameCol", "Name"))
|
|
|
|
+SHeaderRow::Column(FName("Asset"))
|
|
.FillWidth(0.5f)
|
|
.DefaultLabel(LOCTEXT("ParentPlayer_AssetCol", "Asset"))
|
|
);
|
|
|
|
// Expand top level (blueprint) entries so the panel seems less empty
|
|
for (TSharedPtr<FPlayerTreeViewEntry> Entry : ListEntries)
|
|
{
|
|
TreeView->SetItemExpansion(Entry, true);
|
|
}
|
|
|
|
Row
|
|
[
|
|
TreeView->AsShared()
|
|
];
|
|
}
|
|
|
|
TSharedRef<ITableRow> FAnimGraphParentPlayerDetails::OnGenerateRow(TSharedPtr<FPlayerTreeViewEntry> EntryPtr, const TSharedRef< STableViewBase >& OwnerTable)
|
|
{
|
|
return SNew(SParentPlayerTreeRow, OwnerTable).Item(EntryPtr).OverrideObject(EditorObject).BlueprintEditor(BlueprintEditorPtr);
|
|
}
|
|
|
|
void FAnimGraphParentPlayerDetails::OnGetChildren(TSharedPtr<FPlayerTreeViewEntry> InParent, TArray< TSharedPtr<FPlayerTreeViewEntry> >& OutChildren)
|
|
{
|
|
OutChildren.Append(InParent->Children);
|
|
}
|
|
|
|
void SParentPlayerTreeRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
|
|
{
|
|
Item = InArgs._Item;
|
|
EditorObject = InArgs._OverrideObject;
|
|
BlueprintEditor = InArgs._BlueprintEditor;
|
|
|
|
if(Item->Override)
|
|
{
|
|
GraphNode = EditorObject->GetVisualNodeFromGuid(Item->Override->ParentNodeGuid);
|
|
}
|
|
else
|
|
{
|
|
GraphNode = NULL;
|
|
}
|
|
|
|
SMultiColumnTableRow<TSharedPtr<FAnimGraphParentPlayerDetails>>::Construct(FSuperRowType::FArguments(), InOwnerTableView);
|
|
}
|
|
|
|
TSharedRef<SWidget> SParentPlayerTreeRow::GenerateWidgetForColumn(const FName& ColumnName)
|
|
{
|
|
TSharedPtr<SHorizontalBox> HorizBox;
|
|
SAssignNew(HorizBox, SHorizontalBox);
|
|
|
|
if(ColumnName == "Name")
|
|
{
|
|
HorizBox->AddSlot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SExpanderArrow, SharedThis(this))
|
|
];
|
|
|
|
Item->GenerateNameWidget(HorizBox);
|
|
}
|
|
else if(Item->Override)
|
|
{
|
|
HorizBox->AddSlot()
|
|
.Padding(2)
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("FocusNodeButtonTip", "Open the graph that contains this node in read-only mode and focus on the node"), NULL, "Shared/Editors/Persona", "FocusNodeButton"))
|
|
.OnClicked(FOnClicked::CreateSP(this, &SParentPlayerTreeRow::OnFocusNodeButtonClicked))
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("GenericViewButton"))
|
|
]
|
|
|
|
];
|
|
|
|
TArray<const UClass*> AllowedClasses;
|
|
AllowedClasses.Add(UAnimationAsset::StaticClass());
|
|
HorizBox->AddSlot()
|
|
.VAlign(VAlign_Center)
|
|
.FillWidth(1.f)
|
|
[
|
|
SNew(SObjectPropertyEntryBox)
|
|
.ObjectPath(this, &SParentPlayerTreeRow::GetCurrentAssetPath)
|
|
.OnShouldFilterAsset(this, &SParentPlayerTreeRow::OnShouldFilterAsset)
|
|
.OnObjectChanged(this, &SParentPlayerTreeRow::OnAssetSelected)
|
|
.AllowedClass(GetCurrentAssetToUse()->GetClass())
|
|
];
|
|
|
|
HorizBox->AddSlot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
|
|
.Visibility(this, &SParentPlayerTreeRow::GetResetToDefaultVisibility)
|
|
.OnClicked(this, &SParentPlayerTreeRow::OnResetButtonClicked)
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(LOCTEXT("ResetToParentButtonTip", "Undo the override, returning to the default asset for this node"), NULL, "Shared/Editors/Persona", "ResetToParentButton"))
|
|
.Content()
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"))
|
|
]
|
|
];
|
|
}
|
|
|
|
return HorizBox.ToSharedRef();
|
|
}
|
|
|
|
bool SParentPlayerTreeRow::OnShouldFilterAsset(const FAssetData& AssetData)
|
|
{
|
|
const USkeleton* CurrentSkeleton = GraphNode->GetAnimBlueprint()->TargetSkeleton;
|
|
return !CurrentSkeleton->IsCompatibleSkeletonByAssetData(AssetData);
|
|
}
|
|
|
|
void SParentPlayerTreeRow::OnAssetSelected(const FAssetData& AssetData)
|
|
{
|
|
Item->Override->NewAsset = Cast<UAnimationAsset>(AssetData.GetAsset());
|
|
EditorObject->ApplyOverrideToBlueprint(*Item->Override);
|
|
}
|
|
|
|
FReply SParentPlayerTreeRow::OnFocusNodeButtonClicked()
|
|
{
|
|
TSharedPtr<FBlueprintEditor> SharedBlueprintEditor = BlueprintEditor.Pin();
|
|
if(SharedBlueprintEditor.IsValid())
|
|
{
|
|
if(GraphNode)
|
|
{
|
|
UEdGraph* EdGraph = GraphNode->GetGraph();
|
|
TSharedPtr<SGraphEditor> GraphEditor = SharedBlueprintEditor->OpenGraphAndBringToFront(EdGraph);
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->JumpToNode(GraphNode, false);
|
|
}
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
const UAnimationAsset* SParentPlayerTreeRow::GetCurrentAssetToUse() const
|
|
{
|
|
if(Item->Override->NewAsset)
|
|
{
|
|
return Item->Override->NewAsset;
|
|
}
|
|
|
|
if(GraphNode)
|
|
{
|
|
return GraphNode->GetAnimationAsset();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
EVisibility SParentPlayerTreeRow::GetResetToDefaultVisibility() const
|
|
{
|
|
FAnimParentNodeAssetOverride* HierarchyOverride = EditorObject->GetBlueprint()->GetAssetOverrideForNode(Item->Override->ParentNodeGuid, true);
|
|
|
|
if(HierarchyOverride)
|
|
{
|
|
return Item->Override->NewAsset != HierarchyOverride->NewAsset ? EVisibility::Visible : EVisibility::Hidden;
|
|
}
|
|
|
|
return Item->Override->NewAsset != GraphNode->GetAnimationAsset() ? EVisibility::Visible : EVisibility::Hidden;
|
|
}
|
|
|
|
FReply SParentPlayerTreeRow::OnResetButtonClicked()
|
|
{
|
|
FAnimParentNodeAssetOverride* HierarchyOverride = EditorObject->GetBlueprint()->GetAssetOverrideForNode(Item->Override->ParentNodeGuid, true);
|
|
|
|
Item->Override->NewAsset = HierarchyOverride ? ToRawPtr(HierarchyOverride->NewAsset) : GraphNode->GetAnimationAsset();
|
|
|
|
// Apply will remove the override from the object
|
|
EditorObject->ApplyOverrideToBlueprint(*Item->Override);
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FString SParentPlayerTreeRow::GetCurrentAssetPath() const
|
|
{
|
|
const UAnimationAsset* Asset = GetCurrentAssetToUse();
|
|
return Asset ? Asset->GetPathName() : FString("");
|
|
}
|
|
|
|
FORCENOINLINE bool FPlayerTreeViewEntry::operator==(const FPlayerTreeViewEntry& Other)
|
|
{
|
|
return EntryName == Other.EntryName;
|
|
}
|
|
|
|
void FPlayerTreeViewEntry::GenerateNameWidget(TSharedPtr<SHorizontalBox> Box)
|
|
{
|
|
// Get an appropriate image icon for the row
|
|
const FSlateBrush* EntryImageBrush = NULL;
|
|
switch(EntryType)
|
|
{
|
|
case EPlayerTreeViewEntryType::Blueprint:
|
|
EntryImageBrush = FEditorStyle::GetBrush("ClassIcon.Blueprint");
|
|
break;
|
|
case EPlayerTreeViewEntryType::Graph:
|
|
EntryImageBrush = FEditorStyle::GetBrush("GraphEditor.EventGraph_16x");
|
|
break;
|
|
case EPlayerTreeViewEntryType::Node:
|
|
EntryImageBrush = FEditorStyle::GetBrush("GraphEditor.Default_16x");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Box->AddSlot()
|
|
.VAlign(VAlign_Center)
|
|
.AutoWidth()
|
|
[
|
|
SNew(SImage)
|
|
.Image(EntryImageBrush)
|
|
];
|
|
|
|
Box->AddSlot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(FMargin(5.0f, 0.0f, 0.0f, 0.0f))
|
|
.AutoWidth()
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
|
|
.Text(FText::FromString(EntryName))
|
|
];
|
|
}
|
|
|
|
void FAnimGraphNodeBindingExtension::GetOptionalPinData(const IPropertyHandle& PropertyHandle, int32& OutOptionalPinIndex, UAnimGraphNode_Base*& OutAnimGraphNode) const
|
|
{
|
|
OutOptionalPinIndex = INDEX_NONE;
|
|
|
|
TArray<UObject*> Objects;
|
|
PropertyHandle.GetOuterObjects(Objects);
|
|
|
|
FProperty* Property = PropertyHandle.GetProperty();
|
|
if(Property)
|
|
{
|
|
OutAnimGraphNode = Cast<UAnimGraphNode_Base>(Objects[0]);
|
|
if (OutAnimGraphNode != nullptr)
|
|
{
|
|
OutOptionalPinIndex = OutAnimGraphNode->ShowPinForProperties.IndexOfByPredicate([Property](const FOptionalPinFromProperty& InOptionalPin)
|
|
{
|
|
return Property->GetFName() == InOptionalPin.PropertyName;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FAnimGraphNodeBindingExtension::IsPropertyExtendable(const UClass* InObjectClass, const IPropertyHandle& PropertyHandle) const
|
|
{
|
|
int32 OptionalPinIndex;
|
|
UAnimGraphNode_Base* AnimGraphNode;
|
|
GetOptionalPinData(PropertyHandle, OptionalPinIndex, AnimGraphNode);
|
|
|
|
if(OptionalPinIndex != INDEX_NONE)
|
|
{
|
|
const FOptionalPinFromProperty& OptionalPin = AnimGraphNode->ShowPinForProperties[OptionalPinIndex];
|
|
|
|
// Not optional
|
|
if (!OptionalPin.bCanToggleVisibility && OptionalPin.bShowPin)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!PropertyHandle.GetProperty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return OptionalPin.bCanToggleVisibility;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<SWidget> FAnimGraphNodeBindingExtension::GenerateExtensionWidget(const IDetailLayoutBuilder& InDetailBuilder, const UClass* InObjectClass, TSharedPtr<IPropertyHandle> InPropertyHandle)
|
|
{
|
|
int32 OptionalPinIndex;
|
|
UAnimGraphNode_Base* AnimGraphNode;
|
|
GetOptionalPinData(*InPropertyHandle.Get(), OptionalPinIndex, AnimGraphNode);
|
|
check(OptionalPinIndex != INDEX_NONE);
|
|
|
|
TArray<UObject*> OuterObjects;
|
|
InPropertyHandle->GetOuterObjects(OuterObjects);
|
|
|
|
TArray<UAnimGraphNode_Base*> AnimGraphNodes;
|
|
Algo::Transform(OuterObjects, AnimGraphNodes, [](UObject* InObject){ return Cast<UAnimGraphNode_Base>(InObject); });
|
|
|
|
|
|
FProperty* AnimNodeProperty = InPropertyHandle->GetProperty();
|
|
TSharedPtr<IPropertyHandle> ParentHandle = InPropertyHandle->GetParentHandle();
|
|
FProperty* ParentProperty = ParentHandle.IsValid() ? ParentHandle->GetProperty() : nullptr;
|
|
|
|
FName PropertyName;
|
|
if(FArrayProperty* ParentArrayProperty = CastField<FArrayProperty>(ParentProperty))
|
|
{
|
|
int32 ArrayIndex = InPropertyHandle->GetIndexInArray();
|
|
check(ArrayIndex != INDEX_NONE);
|
|
PropertyName = FName(ParentArrayProperty->GetFName(), InPropertyHandle->GetIndexInArray() + 1);
|
|
AnimNodeProperty = ParentArrayProperty;
|
|
}
|
|
else
|
|
{
|
|
PropertyName = AnimNodeProperty->GetFName();
|
|
}
|
|
|
|
const FName OptionalPinArrayEntryName(*FString::Printf(TEXT("ShowPinForProperties[%d].bShowPin"), OptionalPinIndex));
|
|
TSharedRef<IPropertyHandle> ShowPinPropertyHandle = InDetailBuilder.GetProperty(OptionalPinArrayEntryName, UAnimGraphNode_Base::StaticClass());
|
|
ShowPinPropertyHandle->MarkHiddenByCustomization();
|
|
|
|
UAnimGraphNode_Base::FAnimPropertyBindingWidgetArgs Args(AnimGraphNodes, AnimNodeProperty, PropertyName, OptionalPinIndex);
|
|
Args.bOnGraphNode = false;
|
|
|
|
return UAnimGraphNode_Base::MakePropertyBindingWidget(Args);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|