// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "AnimGraphNode_CustomProperty.h" #include "EdGraphSchema_K2.h" #include "Kismet2/CompilerResultsLog.h" #include "Kismet2/BlueprintEditorUtils.h" #include "DetailLayoutBuilder.h" #include "AnimationGraphSchema.h" #include "Animation/AnimInstance.h" #include "Animation/AnimLayerInterface.h" #include "DetailCategoryBuilder.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/SBoxPanel.h" #include "DetailWidgetRow.h" #include "PropertyCustomizationHelpers.h" #define LOCTEXT_NAMESPACE "CustomPropNode" void UAnimGraphNode_CustomProperty::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) { Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog); UAnimBlueprint* AnimBP = CastChecked(GetBlueprint()); UObject* OriginalNode = MessageLog.FindSourceObject(this); if(NeedsToSpecifyValidTargetClass()) { // Check we have a class set UClass* TargetClass = GetTargetClass(); if(!TargetClass) { MessageLog.Error(TEXT("Sub instance node @@ has no valid instance class to spawn."), this); } } } void UAnimGraphNode_CustomProperty::ReallocatePinsDuringReconstruction(TArray& OldPins) { Super::ReallocatePinsDuringReconstruction(OldPins); // Grab the SKELETON class here as when we are reconstructed during during BP compilation // the full generated class is not yet present built. UClass* TargetClass = GetTargetSkeletonClass(); if(!TargetClass) { // Nothing to search for properties return; } // Need the schema to extract pin types const UEdGraphSchema_K2* Schema = CastChecked(GetSchema()); // Default anim schema for util funcions const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault(); // Grab the list of properties we can expose TArray ExposablePropeties; GetExposableProperties(ExposablePropeties); // We'll track the names we encounter by removing from this list, if anything remains the properties // have been removed from the target class and we should remove them too TArray BeginExposableNames = KnownExposableProperties; for(UProperty* Property : ExposablePropeties) { FName PropertyName = Property->GetFName(); BeginExposableNames.Remove(PropertyName); if(!KnownExposableProperties.Contains(PropertyName)) { // New property added to the target class KnownExposableProperties.Add(PropertyName); } if(ExposedPropertyNames.Contains(PropertyName) && FBlueprintEditorUtils::PropertyStillExists(Property)) { FEdGraphPinType PinType; verify(Schema->ConvertPropertyToPinType(Property, PinType)); UEdGraphPin* NewPin = CreatePin(EEdGraphPinDirection::EGPD_Input, PinType, Property->GetFName()); NewPin->PinFriendlyName = Property->GetDisplayNameText(); // We cant interrogate CDO here as we may be mid-compile, so we can only really // reset to the autogenerated default. AnimGraphDefaultSchema->ResetPinToAutogeneratedDefaultValue(NewPin, false); CustomizePinData(NewPin, PropertyName, INDEX_NONE); } } // Remove any properties that no longer exist on the target class for(FName& RemovedPropertyName : BeginExposableNames) { KnownExposableProperties.Remove(RemovedPropertyName); } } void UAnimGraphNode_CustomProperty::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { Super::CustomizeDetails(DetailBuilder); // We dont allow multi-select here if(DetailBuilder.GetSelectedObjects().Num() > 1) { DetailBuilder.HideCategory(TEXT("Settings")); return; } TArray ExposableProperties; GetExposableProperties(ExposableProperties); if(ExposableProperties.Num() > 0) { IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(FName(TEXT("Exposable Properties"))); FDetailWidgetRow& HeaderWidgetRow = CategoryBuilder.AddCustomRow(LOCTEXT("ExposeAll", "Expose All")); HeaderWidgetRow.NameContent() [ SNew(STextBlock) .Text(LOCTEXT("PropertyName", "Name")) .Font(IDetailLayoutBuilder::GetDetailFontBold()) ]; HeaderWidgetRow.ValueContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("ExposeAllPropertyValue", "Expose All")) .Font(IDetailLayoutBuilder::GetDetailFontBold()) ] +SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Center) [ SNew(SCheckBox) .IsChecked_UObject(this, &UAnimGraphNode_CustomProperty::AreAllPropertiesExposed) .OnCheckStateChanged_UObject(this, &UAnimGraphNode_CustomProperty::OnPropertyExposeAllCheckboxChanged) ] ]; for(UProperty* Property : ExposableProperties) { FDetailWidgetRow& PropertyWidgetRow = CategoryBuilder.AddCustomRow(FText::FromString(Property->GetName())); FName PropertyName = Property->GetFName(); FText PropertyTypeText = GetPropertyTypeText(Property); FFormatNamedArguments Args; Args.Add(TEXT("PropertyName"), FText::FromName(PropertyName)); Args.Add(TEXT("PropertyType"), PropertyTypeText); FText TooltipText = FText::Format(LOCTEXT("PropertyTooltipText", "{PropertyName}\nType: {PropertyType}"), Args); PropertyWidgetRow.NameContent() [ SNew(STextBlock) .Text(FText::FromString(Property->GetName())) .ToolTipText(TooltipText) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; PropertyWidgetRow.ValueContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("ExposePropertyValue", "Expose:")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] +SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Center) [ SNew(SCheckBox) .IsChecked_UObject(this, &UAnimGraphNode_CustomProperty::IsPropertyExposed, PropertyName) .OnCheckStateChanged_UObject(this, &UAnimGraphNode_CustomProperty::OnPropertyExposeCheckboxChanged, PropertyName) ] ]; } } IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(FName(TEXT("Settings"))); // Customize InstanceClass { TSharedRef ClassHandle = DetailBuilder.GetProperty(TEXT("Node.InstanceClass"), GetClass()); if(ClassHandle->IsValidHandle()) { ClassHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateUObject(this, &UAnimGraphNode_CustomProperty::OnStructuralPropertyChanged, &DetailBuilder)); } } } void UAnimGraphNode_CustomProperty::GetInstancePinProperty(const UClass* InOwnerInstanceClass, UEdGraphPin* InInputPin, UProperty*& OutProperty) { // The actual name of the instance property FString FullName = GetPinTargetVariableName(InInputPin); if(UProperty* Property = FindField(InOwnerInstanceClass, *FullName)) { OutProperty = Property; } else { OutProperty = nullptr; } } FString UAnimGraphNode_CustomProperty::GetPinTargetVariableName(const UEdGraphPin* InPin) const { return TEXT("__CustomProperty_") + InPin->PinName.ToString() + TEXT("_") + NodeGuid.ToString(); } FText UAnimGraphNode_CustomProperty::GetPropertyTypeText(UProperty* Property) { FText PropertyTypeText; if(UStructProperty* StructProperty = Cast(Property)) { PropertyTypeText = StructProperty->Struct->GetDisplayNameText(); } else if(UObjectProperty* ObjectProperty = Cast(Property)) { PropertyTypeText = ObjectProperty->PropertyClass->GetDisplayNameText(); } else if(UClass* PropClass = Property->GetClass()) { PropertyTypeText = PropClass->GetDisplayNameText(); } else { PropertyTypeText = LOCTEXT("PropertyTypeUnknown", "Unknown"); } return PropertyTypeText; } void UAnimGraphNode_CustomProperty::RebuildExposedProperties() { ExposedPropertyNames.Empty(); KnownExposableProperties.Empty(); TArray ExposableProperties; GetExposableProperties(ExposableProperties); for(UProperty* Property : ExposableProperties) { KnownExposableProperties.Add(Property->GetFName()); } } ECheckBoxState UAnimGraphNode_CustomProperty::AreAllPropertiesExposed() const { if(ExposedPropertyNames.Num() == 0) { return ECheckBoxState::Unchecked; } else { for(FName PropertyName : KnownExposableProperties) { if(!ExposedPropertyNames.Contains(PropertyName)) { return ECheckBoxState::Undetermined; } } } return ECheckBoxState::Checked; } void UAnimGraphNode_CustomProperty::OnPropertyExposeAllCheckboxChanged(ECheckBoxState NewState) { if(NewState == ECheckBoxState::Checked) { ExposedPropertyNames = KnownExposableProperties; } else { ExposedPropertyNames.Empty(); } ReconstructNode(); } ECheckBoxState UAnimGraphNode_CustomProperty::IsPropertyExposed(FName PropertyName) const { return ExposedPropertyNames.Contains(PropertyName) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void UAnimGraphNode_CustomProperty::OnPropertyExposeCheckboxChanged(ECheckBoxState NewState, FName PropertyName) { if(NewState == ECheckBoxState::Checked) { ExposedPropertyNames.AddUnique(PropertyName); } else if(NewState == ECheckBoxState::Unchecked) { ExposedPropertyNames.Remove(PropertyName); } ReconstructNode(); } void UAnimGraphNode_CustomProperty::OnInstanceClassChanged(IDetailLayoutBuilder* DetailBuilder) { if(DetailBuilder) { DetailBuilder->ForceRefreshDetails(); } } UObject* UAnimGraphNode_CustomProperty::GetJumpTargetForDoubleClick() const { UClass* InstanceClass = GetTargetClass(); if(InstanceClass) { return InstanceClass->ClassGeneratedBy; } return nullptr; } bool UAnimGraphNode_CustomProperty::HasExternalDependencies(TArray* OptionalOutput /*= NULL*/) const { UClass* InstanceClassToUse = GetTargetClass(); // Add our instance class... If that changes we need a recompile if(InstanceClassToUse && OptionalOutput) { OptionalOutput->AddUnique(InstanceClassToUse); } bool bSuperResult = Super::HasExternalDependencies(OptionalOutput); return InstanceClassToUse || bSuperResult; } void UAnimGraphNode_CustomProperty::GetExposableProperties( TArray& OutExposableProperties) const { OutExposableProperties.Empty(); UClass* TargetClass = GetTargetClass(); if(TargetClass) { const UEdGraphSchema_K2* Schema = CastChecked(GetSchema()); for(TFieldIterator It(TargetClass, EFieldIteratorFlags::IncludeSuper); It; ++It) { UProperty* CurProperty = *It; FEdGraphPinType PinType; if(CurProperty->HasAllPropertyFlags(CPF_Edit | CPF_BlueprintVisible) && CurProperty->HasAllFlags(RF_Public) && Schema->ConvertPropertyToPinType(CurProperty, PinType)) { OutExposableProperties.Add(CurProperty); } } } } void UAnimGraphNode_CustomProperty::AddSourceTargetProperties(const FName& InSourcePropertyName, const FName& InTargetPropertyName) { FAnimNode_CustomProperty* CustomPropAnimNode = GetCustomPropertyNode(); if (CustomPropAnimNode) { CustomPropAnimNode->SourcePropertyNames.Add(InSourcePropertyName); CustomPropAnimNode->DestPropertyNames.Add(InTargetPropertyName); } } UClass* UAnimGraphNode_CustomProperty::GetTargetClass() const { const FAnimNode_CustomProperty* CustomPropAnimNode = GetCustomPropertyNode(); if (CustomPropAnimNode) { return CustomPropAnimNode->GetTargetClass(); } return nullptr; } UClass* UAnimGraphNode_CustomProperty::GetTargetSkeletonClass() const { UClass* TargetClass = GetTargetClass(); if(TargetClass) { UBlueprint* Blueprint = CastChecked(TargetClass->ClassGeneratedBy); if(Blueprint) { if (Blueprint->SkeletonGeneratedClass) { return Blueprint->SkeletonGeneratedClass; } } } return TargetClass; } void UAnimGraphNode_CustomProperty::OnStructuralPropertyChanged(IDetailLayoutBuilder* DetailBuilder) { if(DetailBuilder) { DetailBuilder->ForceRefreshDetails(); } } #undef LOCTEXT_NAMESPACE