Files
UnrealEngine/Engine/Source/Editor/Persona/Private/BlendProfileCustomization.cpp
2025-05-18 13:04:45 +08:00

150 lines
5.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BlendProfileCustomization.h"
#include "Editor.h"
#include "PropertyHandle.h"
#include "DetailWidgetRow.h"
#include "BlendProfilePicker.h"
#include "Animation/BlendProfile.h"
#include "ISkeletonEditorModule.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Animation/AnimBlueprint.h"
#include "Modules/ModuleManager.h"
#include "Engine/PoseWatch.h"
#include "Animation/BlendSpace.h"
#define LOCTEXT_NAMESPACE "BlendProfileCustomization"
namespace BlendProfileCustomizationNames
{
static const FName UseAsBlendProfileName = FName(TEXT("UseAsBlendProfile"));
static const FName UseAsBlendMaskName = FName(TEXT("UseAsBlendMask"));
}
TSharedRef<IPropertyTypeCustomization> FBlendProfileCustomization::MakeInstance()
{
return MakeShareable(new FBlendProfileCustomization);
}
void FBlendProfileCustomization::CustomizeHeader(TSharedRef<class IPropertyHandle> InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
{
TSharedRef<SWidget> ValueCustomWidget = SNullWidget::NullWidget;
TArray<UObject*> OuterObjects;
InStructPropertyHandle->GetOuterObjects(OuterObjects);
if (OuterObjects.Num() > 0)
{
// try to get skeleton from first outer
if (USkeleton* TargetSkeleton = GetSkeletonFromOuter(OuterObjects[0]))
{
TWeakPtr<IPropertyHandle> PropertyPtr(InStructPropertyHandle);
UObject* PropertyValue = nullptr;
InStructPropertyHandle->GetValue(PropertyValue);
UBlendProfile* CurrentProfile = Cast<UBlendProfile>(PropertyValue);
const bool bUseAsBlendMask = InStructPropertyHandle->GetBoolMetaData(BlendProfileCustomizationNames::UseAsBlendMaskName);
const bool bUseAsBlendProfile = InStructPropertyHandle->GetBoolMetaData(BlendProfileCustomizationNames::UseAsBlendProfileName);
// If no mode is defined, show both.
EBlendProfilePickerMode SupportedBlendProfileModes = (bUseAsBlendMask || bUseAsBlendProfile) ? EBlendProfilePickerMode(0) : EBlendProfilePickerMode::AllModes;
if (bUseAsBlendProfile)
{
SupportedBlendProfileModes |= EBlendProfilePickerMode::BlendProfile;
}
if (bUseAsBlendMask)
{
SupportedBlendProfileModes |= EBlendProfilePickerMode::BlendMask;
}
FBlendProfilePickerArgs Args;
Args.bAllowNew = false;
Args.bAllowModify = false;
Args.bAllowClear = true;
Args.OnBlendProfileSelected = FOnBlendProfileSelected::CreateSP(this, &FBlendProfileCustomization::OnBlendProfileChanged, PropertyPtr);
Args.InitialProfile = CurrentProfile;
Args.SupportedBlendProfileModes = SupportedBlendProfileModes;
Args.PropertyHandle = InStructPropertyHandle;
ISkeletonEditorModule& SkeletonEditorModule = FModuleManager::Get().LoadModuleChecked<ISkeletonEditorModule>("SkeletonEditor");
ValueCustomWidget = SkeletonEditorModule.CreateBlendProfilePicker(TargetSkeleton, Args);
}
}
HeaderRow.NameContent()
[
InStructPropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
.MinDesiredWidth(125.f)
.MaxDesiredWidth(400.f) //Slightly wider since expected names are a bit longer if users use BlendProfileModes as suffix
[
// If we can't find a skeleton, we can't use custom SWidget. Default to regular one.
ValueCustomWidget != SNullWidget::NullWidget ? ValueCustomWidget : InStructPropertyHandle->CreatePropertyValueWidget()
];
}
void FBlendProfileCustomization::OnBlendProfileChanged(UBlendProfile* NewProfile, TWeakPtr<IPropertyHandle> WeakPropertyHandle)
{
if(!GIsTransacting)
{
if (TSharedPtr<IPropertyHandle> PropertyHandle = WeakPropertyHandle.Pin())
{
PropertyHandle->SetValue(NewProfile);
}
}
}
USkeleton* FBlendProfileCustomization::GetSkeletonFromOuter(const UObject* Outer)
{
const UAnimBlueprint* AnimBlueprint = nullptr;
if (const UBlendSpace* BlendSpace = Cast<UBlendSpace>(Outer))
{
// Check for blend space graph nodes
if (!BlendSpace->IsAsset())
{
AnimBlueprint = BlendSpace->GetTypedOuter<UAnimBlueprint>();
}
}
if (const UEdGraphNode* OuterEdGraphNode = Cast<UEdGraphNode>(Outer))
{
AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(OuterEdGraphNode));
}
else if (const UEdGraph* OuterEdGraph = Cast<UEdGraph>(Outer))
{
AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(OuterEdGraph));
}
else if (const UPoseWatch* OuterPoseWatch = Cast<UPoseWatch>(Outer))
{
AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(OuterPoseWatch->Node.Get()));
}
else if (const UPoseWatchPoseElement* OuterPoseElement = Cast<UPoseWatchPoseElement>(Outer))
{
if (const UPoseWatch* OuterPoseElementParent = OuterPoseElement->GetParent())
{
AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(OuterPoseElementParent->Node.Get()));
}
}
// If outer belongs to an anim blueprint, grab its skeleton.
if (AnimBlueprint)
{
return AnimBlueprint->TargetSkeleton;
}
if (const UAnimationAsset* OuterAnimAsset = Cast<UAnimationAsset>(Outer))
{
// If outer is an anim asset, grab the skeleton
return OuterAnimAsset->GetSkeleton();
}
return nullptr;
}
#undef LOCTEXT_NAMESPACE