2021-03-19 15:10:57 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "MetasoundNodeDetailCustomization.h"
# include "Containers/Set.h"
# include "CoreMinimal.h"
# include "Delegates/Delegate.h"
# include "DetailCategoryBuilder.h"
# include "DetailLayoutBuilder.h"
# include "DetailWidgetRow.h"
# include "Framework/Notifications/NotificationManager.h"
# include "IDetailChildrenBuilder.h"
# include "IDetailGroup.h"
# include "Internationalization/Text.h"
# include "MetasoundAssetBase.h"
# include "MetasoundEditorGraphBuilder.h"
# include "MetasoundEditorGraphNode.h"
# include "MetasoundEditorGraphInputNodes.h"
2021-03-25 23:46:38 -04:00
# include "MetasoundEditorModule.h"
2021-03-19 15:10:57 -04:00
# include "MetasoundFrontend.h"
# include "MetasoundFrontendController.h"
# include "MetasoundFrontendRegistries.h"
# include "MetasoundUObjectRegistry.h"
# include "PropertyCustomizationHelpers.h"
# include "PropertyEditorDelegates.h"
# include "PropertyHandle.h"
# include "PropertyRestriction.h"
# include "SlateCore/Public/Styling/SlateColor.h"
# include "Templates/Casts.h"
# include "Templates/SharedPointer.h"
# include "UObject/WeakObjectPtr.h"
# include "UObject/WeakObjectPtrTemplates.h"
2021-03-25 23:46:38 -04:00
# include "Widgets/Images/SImage.h"
# include "Widgets/Input/SButton.h"
2021-03-19 15:10:57 -04:00
# include "Widgets/Input/SCheckBox.h"
# include "Widgets/Input/SMultiLineEditableTextBox.h"
# include "Widgets/Input/STextComboBox.h"
# include "Widgets/Notifications/SNotificationList.h"
# include "Widgets/SToolTip.h"
# include "Widgets/SWidget.h"
# include "Widgets/Text/STextBlock.h"
2021-04-02 03:03:27 -04:00
# define LOCTEXT_NAMESPACE "MetaSoundEditor"
2021-03-19 15:10:57 -04:00
namespace Metasound
{
namespace Editor
{
namespace VariableCustomizationPrivate
{
/** Minimum size of the details title panel */
static const float DetailsTitleMinWidth = 125.f ;
/** Maximum size of the details title panel */
static const float DetailsTitleMaxWidth = 300.f ;
/** magic number retrieved from SGraphNodeComment::GetWrapAt() */
static const float DetailsTitleWrapPadding = 32.0f ;
static const FString ArrayIdentifier = TEXT ( " :Array " ) ;
static const FText DataTypeNameText = LOCTEXT ( " Node_DataTypeName " , " Type " ) ;
static const FText DefaultPropertyText = LOCTEXT ( " Node_DefaultPropertyName " , " Default Value " ) ;
static const FText NodeTooltipText = LOCTEXT ( " Node_Tooltip " , " Tooltip " ) ;
static const FText InputNameText = LOCTEXT ( " Input_Name " , " Input Name " ) ;
static const FText OutputNameText = LOCTEXT ( " Output_Name " , " Output Name " ) ;
2021-03-23 17:55:31 -04:00
static const FName TriggerTypeName = " Trigger " ;
2021-03-19 15:10:57 -04:00
static const FName DataTypeNameIdentifier = " DataTypeName " ;
static const FName ProxyGeneratorClassNameIdentifier = " GeneratorClass " ;
2021-03-23 17:55:31 -04:00
void ExecuteInputTrigger ( UMetasoundEditorGraphInputLiteral * Literal )
{
if ( ! Literal )
{
return ;
}
UMetasoundEditorGraphInput * Input = Cast < UMetasoundEditorGraphInput > ( Literal - > GetOuter ( ) ) ;
if ( ! ensure ( Input ) )
{
return ;
}
// TODO: fix how identifying the parameter to update is determined. It should not be done
// with a "DisplayName" but rather the vertex Guid.
if ( UMetasoundEditorGraph * MetasoundGraph = Cast < UMetasoundEditorGraph > ( Input - > GetOuter ( ) ) )
{
if ( IAudioInstanceTransmitter * Transmitter = MetasoundGraph - > GetMetasoundInstanceTransmitter ( ) )
{
Metasound : : Frontend : : FConstNodeHandle NodeHandle = Input - > GetConstNodeHandle ( ) ;
Metasound : : FVertexKey VertexKey = Metasound : : FVertexKey ( NodeHandle - > GetDisplayName ( ) . ToString ( ) ) ;
Transmitter - > SetParameter ( * VertexKey , true ) ;
}
}
}
}
void FMetasoundInputBoolDetailCustomization : : CacheProxyData ( TSharedPtr < IPropertyHandle > ProxyHandle )
{
DataTypeName = FName ( ) ;
const FString * MetadataDataTypeName = ProxyHandle - > GetInstanceMetaData ( VariableCustomizationPrivate : : DataTypeNameIdentifier ) ;
if ( ensure ( MetadataDataTypeName ) )
{
DataTypeName = * * MetadataDataTypeName ;
}
}
FText FMetasoundInputBoolDetailCustomization : : GetPropertyNameOverride ( ) const
{
if ( DataTypeName = = VariableCustomizationPrivate : : TriggerTypeName )
{
return LOCTEXT ( " TriggerInput_SimulateTitle " , " Simulate " ) ;
}
return FText : : GetEmpty ( ) ;
}
TSharedRef < SWidget > FMetasoundInputBoolDetailCustomization : : CreateStructureWidget ( TSharedPtr < IPropertyHandle > & StructPropertyHandle ) const
{
using namespace Frontend ;
if ( FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) )
{
TSharedPtr < IPropertyHandle > ValueProperty = StructPropertyHandle - > GetChildHandle ( GET_MEMBER_NAME_CHECKED ( FMetasoundEditorGraphInputBoolRef , Value ) ) ;
if ( ValueProperty . IsValid ( ) )
{
// Not a trigger, so just display as underlying literal type (bool)
if ( DataTypeName ! = VariableCustomizationPrivate : : TriggerTypeName )
{
return ValueProperty - > CreatePropertyValueWidget ( ) ;
}
return
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. AutoWidth ( )
. Padding ( 2.0f , 0.0f , 0.0f , 0.0f )
. VAlign ( VAlign_Center )
[
SNew ( SButton )
. ButtonStyle ( FAppStyle : : Get ( ) , " SimpleButton " )
. OnClicked_Lambda ( [ ValueProperty ] ( )
{
TArray < UObject * > OuterObjects ;
ValueProperty - > GetOuterObjects ( OuterObjects ) ;
for ( UObject * Object : OuterObjects )
{
UMetasoundEditorGraphInputLiteral * Literal = Cast < UMetasoundEditorGraphInputLiteral > ( Object ) ;
VariableCustomizationPrivate : : ExecuteInputTrigger ( Literal ) ;
}
return FReply : : Handled ( ) ;
} )
. ToolTipText ( LOCTEXT ( " TriggerTestToolTip " , " Executes trigger if currently previewing Metasound. " ) )
. ForegroundColor ( FSlateColor : : UseForeground ( ) )
. ContentPadding ( 0 )
. IsFocusable ( false )
[
SNew ( SImage )
. Image ( FAppStyle : : Get ( ) . GetBrush ( " Icons.CircleArrowDown " ) )
. ColorAndOpacity ( FSlateColor : : UseForeground ( ) )
]
] ;
}
}
return SNullWidget : : NullWidget ;
2021-03-19 15:10:57 -04:00
}
void FMetasoundInputIntDetailCustomization : : CacheProxyData ( TSharedPtr < IPropertyHandle > ProxyHandle )
{
DataTypeName = FName ( ) ;
const FString * MetadataDataTypeName = ProxyHandle - > GetInstanceMetaData ( VariableCustomizationPrivate : : DataTypeNameIdentifier ) ;
if ( ensure ( MetadataDataTypeName ) )
{
DataTypeName = * * MetadataDataTypeName ;
}
}
TSharedRef < SWidget > FMetasoundInputIntDetailCustomization : : CreateStructureWidget ( TSharedPtr < IPropertyHandle > & StructPropertyHandle ) const
{
using namespace Frontend ;
if ( FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) )
{
TSharedPtr < IPropertyHandle > ValueProperty = StructPropertyHandle - > GetChildHandle ( GET_MEMBER_NAME_CHECKED ( FMetasoundEditorGraphInputIntRef , Value ) ) ;
if ( ValueProperty . IsValid ( ) )
{
TSharedPtr < const IEnumDataTypeInterface > EnumInterface = Registry - > GetEnumInterfaceForDataType ( DataTypeName ) ;
// Not an enum, so just display as underlying type (int32)
if ( ! EnumInterface . IsValid ( ) )
{
return ValueProperty - > CreatePropertyValueWidget ( ) ;
}
auto GetAll = [ Interface = EnumInterface ] ( TArray < TSharedPtr < FString > > & OutStrings , TArray < TSharedPtr < SToolTip > > & OutTooltips , TArray < bool > & )
{
for ( const IEnumDataTypeInterface : : FGenericInt32Entry & i : Interface - > GetAllEntries ( ) )
{
OutTooltips . Emplace ( SNew ( SToolTip ) . Text ( i . Tooltip ) ) ;
OutStrings . Emplace ( MakeShared < FString > ( i . DisplayName . ToString ( ) ) ) ;
}
} ;
auto GetValue = [ Interface = EnumInterface , Prop = ValueProperty ] ( )
{
int32 IntValue ;
if ( Prop - > GetValue ( IntValue ) ! = FPropertyAccess : : Success )
{
IntValue = Interface - > GetDefaultValue ( ) ;
2021-03-31 00:23:34 -04:00
UE_LOG ( LogMetasoundEditor , Warning , TEXT ( " Failed to read int Property '%s', defaulting. " ) , * GetNameSafe ( Prop - > GetProperty ( ) ) ) ;
2021-03-19 15:10:57 -04:00
}
if ( TOptional < IEnumDataTypeInterface : : FGenericInt32Entry > Result = Interface - > FindByValue ( IntValue ) )
{
return Result - > DisplayName . ToString ( ) ;
}
2021-03-31 00:23:34 -04:00
UE_LOG ( LogMetasoundEditor , Warning , TEXT ( " Failed to resolve int value '%d' to a valid enum value for enum '%s' " ) ,
2021-03-19 15:10:57 -04:00
IntValue , * Interface - > GetNamespace ( ) . ToString ( ) ) ;
// Return default (should always succeed as we can't have empty Enums and we must have a default).
return Interface - > FindByValue ( Interface - > GetDefaultValue ( ) ) - > DisplayName . ToString ( ) ;
} ;
auto SelectedValue = [ Interface = EnumInterface , Prop = ValueProperty ] ( const FString & InSelected )
{
TOptional < IEnumDataTypeInterface : : FGenericInt32Entry > Found =
Interface - > FindEntryBy ( [ TextSelected = FText : : FromString ( InSelected ) ] ( const IEnumDataTypeInterface : : FGenericInt32Entry & i )
{
return i . DisplayName . EqualTo ( TextSelected ) ;
} ) ;
if ( Found )
{
// Only save the changes if its different and we can read the old value to check that.
int32 CurrentValue ;
bool bReadCurrentValue = Prop - > GetValue ( CurrentValue ) = = FPropertyAccess : : Success ;
if ( ( bReadCurrentValue & & CurrentValue ! = Found - > Value ) | | ! bReadCurrentValue )
{
ensure ( Prop - > SetValue ( Found - > Value ) = = FPropertyAccess : : Success ) ;
}
}
else
{
2021-03-31 00:23:34 -04:00
UE_LOG ( LogMetasoundEditor , Warning , TEXT ( " Failed to Set Valid Value for Property '%s' with Value of '%s', writing default. " ) ,
2021-03-19 15:10:57 -04:00
* GetNameSafe ( Prop - > GetProperty ( ) ) , * InSelected ) ;
ensure ( Prop - > SetValue ( Interface - > GetDefaultValue ( ) ) = = FPropertyAccess : : Success ) ;
}
} ;
return PropertyCustomizationHelpers : : MakePropertyComboBox (
nullptr ,
FOnGetPropertyComboBoxStrings : : CreateLambda ( GetAll ) ,
FOnGetPropertyComboBoxValue : : CreateLambda ( GetValue ) ,
FOnPropertyComboBoxValueSelected : : CreateLambda ( SelectedValue )
) ;
}
}
return SNullWidget : : NullWidget ;
}
void FMetasoundInputObjectDetailCustomization : : CacheProxyData ( TSharedPtr < IPropertyHandle > ProxyHandle )
{
ProxyGenClass . Reset ( ) ;
const FString * MetadataProxyGenClass = ProxyHandle - > GetInstanceMetaData ( VariableCustomizationPrivate : : ProxyGeneratorClassNameIdentifier ) ;
TSharedPtr < IPropertyHandle > MetadataHandle = ProxyHandle - > GetParentHandle ( ) ;
if ( ! ensure ( MetadataProxyGenClass ) )
{
return ;
}
const FName ClassName = FName ( * MetadataProxyGenClass ) ;
for ( TObjectIterator < UClass > ClassIt ; ClassIt ; + + ClassIt )
{
UClass * Class = * ClassIt ;
if ( ! Class - > IsNative ( ) )
{
continue ;
}
if ( Class - > HasAnyClassFlags ( CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists ) )
{
continue ;
}
if ( ClassIt - > GetFName ( ) ! = ClassName )
{
continue ;
}
ProxyGenClass = * ClassIt ;
return ;
}
ensureMsgf ( false , TEXT ( " Failed to find ProxyGeneratorClass. Class not set " ) ) ;
}
TSharedRef < SWidget > FMetasoundInputObjectDetailCustomization : : CreateStructureWidget ( TSharedPtr < IPropertyHandle > & StructPropertyHandle ) const
{
TSharedPtr < IPropertyHandle > PropertyHandle = StructPropertyHandle - > GetChildHandle ( GET_MEMBER_NAME_CHECKED ( FMetasoundEditorGraphInputObjectRef , Object ) ) ;
auto ValidateAsset = [ InProxyGenClass = ProxyGenClass ] ( const FAssetData & InAsset )
{
if ( ! InProxyGenClass . IsValid ( ) )
{
return false ;
}
if ( UObject * Object = InAsset . GetAsset ( ) )
{
if ( UClass * Class = Object - > GetClass ( ) )
{
return Class - > IsChildOf ( InProxyGenClass . Get ( ) ) ;
}
}
return false ;
} ;
auto CommitAsset = [ InPropertyHandle = PropertyHandle , InProxyGenClass = ProxyGenClass ] ( const FAssetData & InAssetData )
{
// if we've hit this code, the presumption is that the datatype for this parameter has already defined a corresponding UClass that can be used to set it.
ensureAlways ( InProxyGenClass . IsValid ( ) ) ;
UObject * InObject = InAssetData . GetAsset ( ) ;
InPropertyHandle - > SetValue ( InObject ) ;
} ;
auto GetAssetPath = [ PropertyHandle = PropertyHandle ] ( )
{
UObject * Object = nullptr ;
if ( PropertyHandle - > GetValue ( Object ) = = FPropertyAccess : : Success )
{
return Object - > GetPathName ( ) ;
}
return FString ( ) ;
} ;
TArray < const UClass * > AllowedClasses ;
AllowedClasses . Add ( ProxyGenClass . Get ( ) ) ;
return SNew ( SObjectPropertyEntryBox )
. ObjectPath_Lambda ( GetAssetPath )
. AllowedClass ( ProxyGenClass . Get ( ) )
. OnShouldSetAsset_Lambda ( ValidateAsset )
. OnObjectChanged_Lambda ( CommitAsset )
. AllowClear ( false )
. DisplayUseSelected ( true )
. DisplayBrowse ( true )
. DisplayThumbnail ( true )
. NewAssetFactories ( PropertyCustomizationHelpers : : GetNewAssetFactoriesForClasses ( AllowedClasses ) ) ;
}
2021-03-23 17:55:31 -04:00
TSharedRef < SWidget > FMetasoundInputArrayDetailCustomizationBase : : CreateNameWidget ( TSharedPtr < IPropertyHandle > StructPropertyHandle ) const
{
const FText PropertyName = GetPropertyNameOverride ( ) ;
if ( ! PropertyName . IsEmpty ( ) )
{
return SNew ( STextBlock )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
. Text ( PropertyName ) ;
}
return SNew ( STextBlock )
. Text ( VariableCustomizationPrivate : : DefaultPropertyText )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) ) ;
}
2021-03-19 15:10:57 -04:00
TSharedRef < SWidget > FMetasoundInputArrayDetailCustomizationBase : : CreateValueWidget ( TSharedPtr < IPropertyHandleArray > ParentArrayProperty , TSharedPtr < IPropertyHandle > StructPropertyHandle , bool bIsInArray ) const
{
2021-03-23 17:55:31 -04:00
TSharedRef < SWidget > ValueWidget = CreateStructureWidget ( StructPropertyHandle ) ;
2021-03-19 15:10:57 -04:00
if ( ! bIsInArray )
{
2021-03-23 17:55:31 -04:00
return ValueWidget ;
2021-03-19 15:10:57 -04:00
}
TSharedPtr < IPropertyHandle > StructPropertyPtr = StructPropertyHandle ;
FExecuteAction InsertAction = FExecuteAction : : CreateLambda ( [ ParentArrayProperty , StructPropertyPtr ]
{
const int32 ArrayIndex = StructPropertyPtr . IsValid ( ) ? StructPropertyPtr - > GetIndexInArray ( ) : INDEX_NONE ;
if ( ParentArrayProperty . IsValid ( ) & & ArrayIndex > = 0 )
{
ParentArrayProperty - > Insert ( ArrayIndex ) ;
}
} ) ;
FExecuteAction DeleteAction = FExecuteAction : : CreateLambda ( [ ParentArrayProperty , StructPropertyPtr ]
{
const int32 ArrayIndex = StructPropertyPtr . IsValid ( ) ? StructPropertyPtr - > GetIndexInArray ( ) : INDEX_NONE ;
if ( ParentArrayProperty . IsValid ( ) & & ArrayIndex > = 0 )
{
ParentArrayProperty - > DeleteItem ( ArrayIndex ) ;
}
} ) ;
FExecuteAction DuplicateAction = FExecuteAction : : CreateLambda ( [ ParentArrayProperty , StructPropertyPtr ]
{
const int32 ArrayIndex = StructPropertyPtr . IsValid ( ) ? StructPropertyPtr - > GetIndexInArray ( ) : INDEX_NONE ;
if ( ParentArrayProperty . IsValid ( ) & & ArrayIndex > = 0 )
{
ParentArrayProperty - > DuplicateItem ( ArrayIndex ) ;
}
} ) ;
return SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. FillWidth ( 0.95f )
. Padding ( 1.0f , 0.0f , 0.0f , 0.0f )
. VAlign ( VAlign_Center )
[
2021-03-23 17:55:31 -04:00
ValueWidget
2021-03-19 15:10:57 -04:00
]
+ SHorizontalBox : : Slot ( )
. FillWidth ( 0.05f )
. Padding ( 1.0f , 0.0f , 0.0f , 0.0f )
. VAlign ( VAlign_Center )
[
PropertyCustomizationHelpers : : MakeInsertDeleteDuplicateButton ( InsertAction , DeleteAction , DuplicateAction )
] ;
}
void FMetasoundInputArrayDetailCustomizationBase : : CustomizeChildren ( TSharedRef < IPropertyHandle > StructPropertyHandle , IDetailChildrenBuilder & ChildBuilder , IPropertyTypeCustomizationUtils & StructCustomizationUtils )
{
bool bIsInArray = false ;
TSharedPtr < IPropertyHandleArray > ParentArrayProperty ;
TSharedPtr < IPropertyHandle > ProxyProperty = StructPropertyHandle ;
{
TSharedPtr < IPropertyHandle > ParentProperty = ProxyProperty - > GetParentHandle ( ) ;
if ( ProxyProperty . IsValid ( ) & & ParentProperty . IsValid ( ) )
{
ParentArrayProperty = ParentProperty - > AsArray ( ) ;
if ( ParentArrayProperty . IsValid ( ) )
{
ProxyProperty = ParentProperty ;
bIsInArray = true ;
}
}
}
CacheProxyData ( ProxyProperty ) ;
TSharedRef < SWidget > ValueWidget = CreateValueWidget ( ParentArrayProperty , StructPropertyHandle , bIsInArray ) ;
FDetailWidgetRow & ValueRow = ChildBuilder . AddCustomRow ( VariableCustomizationPrivate : : DefaultPropertyText ) ;
if ( bIsInArray )
{
ValueRow . NameContent ( )
[
StructPropertyHandle - > CreatePropertyNameWidget ( )
] ;
}
else
{
ValueRow . NameContent ( )
[
2021-03-23 17:55:31 -04:00
CreateNameWidget ( StructPropertyHandle )
2021-03-19 15:10:57 -04:00
] ;
}
TArray < UObject * > OuterObjects ;
StructPropertyHandle - > GetOuterObjects ( OuterObjects ) ;
TArray < TWeakObjectPtr < UMetasoundEditorGraphInput > > Inputs ;
for ( UObject * Object : OuterObjects )
{
if ( UMetasoundEditorGraphInput * Input = Cast < UMetasoundEditorGraphInput > ( Object ) )
{
Inputs . Add ( Input ) ;
}
}
FSimpleDelegate OnLiteralChanged = FSimpleDelegate : : CreateLambda ( [ InInputs = Inputs ] ( )
{
for ( const TWeakObjectPtr < UMetasoundEditorGraphInput > & GraphInput : InInputs )
{
if ( GraphInput . IsValid ( ) )
{
GraphInput - > OnLiteralChanged ( ) ;
}
}
} ) ;
StructPropertyHandle - > SetOnChildPropertyValueChanged ( OnLiteralChanged ) ;
ValueRow . ValueContent ( )
[
ValueWidget
] ;
}
void FMetasoundInputArrayDetailCustomizationBase : : CustomizeHeader ( TSharedRef < IPropertyHandle > StructPropertyHandle , FDetailWidgetRow & HeaderRow , IPropertyTypeCustomizationUtils & StructCustomizationUtils )
{
}
void FMetasoundVariableDataTypeSelector : : AddDataTypeSelector ( IDetailLayoutBuilder & InDetailLayout , const FText & InRowName , TWeakObjectPtr < UMetasoundEditorGraphVariable > InGraphVariable , bool bIsRequired )
{
DetailLayoutBuilder = & InDetailLayout ;
IDetailCategoryBuilder & CategoryBuilder = InDetailLayout . EditCategory ( " General " ) ;
TSharedPtr < FString > CurrentTypeString ;
FString CurrentTypeName = InGraphVariable - > TypeName . ToString ( ) ;
bool bCurrentTypeIsArray = CurrentTypeName . EndsWith ( VariableCustomizationPrivate : : ArrayIdentifier ) ;
if ( bCurrentTypeIsArray )
{
CurrentTypeName . LeftChopInline ( VariableCustomizationPrivate : : ArrayIdentifier . Len ( ) ) ;
}
DataTypeNames . Reset ( ) ;
2021-04-02 03:03:27 -04:00
IMetasoundEditorModule & EditorModule = FModuleManager : : GetModuleChecked < IMetasoundEditorModule > ( " MetaSoundEditor " ) ;
2021-03-19 15:10:57 -04:00
EditorModule . IterateDataTypes ( [ & ] ( const FEditorDataType & EditorDataType )
{
const FString TypeName = EditorDataType . RegistryInfo . DataTypeName . ToString ( ) ;
// Array types are handles separately via checkbox
if ( TypeName . EndsWith ( VariableCustomizationPrivate : : ArrayIdentifier ) )
{
return ;
}
TSharedPtr < FString > TypeStrPtr = MakeShared < FString > ( TypeName ) ;
if ( TypeName = = CurrentTypeName )
{
CurrentTypeString = TypeStrPtr ;
}
DataTypeNames . Add ( TypeStrPtr ) ;
} ) ;
if ( ! ensure ( CurrentTypeString . IsValid ( ) ) )
{
return ;
}
DataTypeNames . Sort ( [ ] ( const TSharedPtr < FString > & DataTypeNameL , const TSharedPtr < FString > & DataTypeNameR )
{
if ( DataTypeNameL . IsValid ( ) & & DataTypeNameR . IsValid ( ) )
{
return DataTypeNameR - > Compare ( * DataTypeNameL . Get ( ) ) > 0 ;
}
return false ;
} ) ;
CategoryBuilder . AddCustomRow ( InRowName )
. IsEnabled ( ! bIsRequired )
. NameContent ( )
[
SNew ( STextBlock )
. Text ( InRowName )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
. ValueContent ( )
[
SNew ( SHorizontalBox )
+ SHorizontalBox : : Slot ( )
. FillWidth ( 0.60f )
. Padding ( 1.0f , 0.0f , 0.0f , 0.0f )
. VAlign ( VAlign_Center )
[
SAssignNew ( DataTypeComboBox , STextComboBox )
. OptionsSource ( & DataTypeNames )
. InitiallySelectedItem ( CurrentTypeString )
. OnSelectionChanged_Lambda ( [ this , InGraphVariable ] ( TSharedPtr < FString > ItemSelected , ESelectInfo : : Type SelectInfo )
{
OnBaseDataTypeChanged ( InGraphVariable , ItemSelected , SelectInfo ) ;
} )
. IsEnabled ( ! bIsRequired )
]
+ SHorizontalBox : : Slot ( )
. FillWidth ( 0.40f )
2021-03-23 17:55:31 -04:00
. Padding ( 2.0f , 0.0f , 0.0f , 0.0f )
2021-03-19 15:10:57 -04:00
. VAlign ( VAlign_Center )
[
SAssignNew ( DataTypeArrayCheckbox , SCheckBox )
. IsChecked_Lambda ( [ this , InGraphVariable ] ( )
{
return OnGetDataTypeArrayCheckState ( InGraphVariable ) ;
} )
. OnCheckStateChanged_Lambda ( [ this , InGraphVariable ] ( ECheckBoxState InNewState )
{
OnDataTypeArrayChanged ( InGraphVariable , InNewState ) ;
} )
[
SNew ( STextBlock )
. Text ( LOCTEXT ( " Node_IsArray " , " Is Array " ) )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
]
] ;
}
ECheckBoxState FMetasoundVariableDataTypeSelector : : OnGetDataTypeArrayCheckState ( TWeakObjectPtr < UMetasoundEditorGraphVariable > InGraphVariable ) const
{
if ( InGraphVariable . IsValid ( ) )
{
FString CurrentTypeName = InGraphVariable - > TypeName . ToString ( ) ;
bool bCurrentTypeIsArray = CurrentTypeName . EndsWith ( VariableCustomizationPrivate : : ArrayIdentifier ) ;
return bCurrentTypeIsArray ? ECheckBoxState : : Checked : ECheckBoxState : : Unchecked ;
}
return ECheckBoxState : : Undetermined ;
}
void FMetasoundInputDetailCustomization : : CustomizeDetails ( IDetailLayoutBuilder & DetailLayout )
{
using namespace Frontend ;
TMetasoundVariableDetailCustomization < UMetasoundEditorGraphInput > : : CustomizeDetails ( DetailLayout ) ;
if ( ! GraphVariable . IsValid ( ) )
{
return ;
}
IDetailCategoryBuilder & CategoryBuilder = DetailLayout . EditCategory ( " General " ) ;
const bool bIsRequired = IsRequired ( ) ;
DisplayNameEditableTextBox = SNew ( SEditableTextBox )
. Text ( this , & FMetasoundInputDetailCustomization : : GetDisplayName )
. OnTextChanged ( this , & FMetasoundInputDetailCustomization : : OnDisplayNameChanged )
. OnTextCommitted ( this , & FMetasoundInputDetailCustomization : : OnDisplayNameCommitted )
. IsReadOnly ( bIsRequired )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) ) ;
CategoryBuilder . AddCustomRow ( VariableCustomizationPrivate : : InputNameText )
. EditCondition ( ! bIsRequired , nullptr )
. NameContent ( )
[
SNew ( STextBlock )
. Font ( IDetailLayoutBuilder : : GetDetailFontBold ( ) )
. Text ( VariableCustomizationPrivate : : InputNameText )
. ToolTipText ( TAttribute < FText > : : Create ( [ GraphVariable = this - > GraphVariable ] ( )
{
if ( GraphVariable . IsValid ( ) )
{
FNodeHandle NodeHandle = GraphVariable - > GetNodeHandle ( ) ;
FMetasoundFrontendNodeStyle NodeStyle = NodeHandle - > GetNodeStyle ( ) ;
return NodeHandle - > GetDescription ( ) ;
}
return FText : : GetEmpty ( ) ;
} ) )
]
. ValueContent ( )
[
DisplayNameEditableTextBox . ToSharedRef ( )
] ;
CategoryBuilder . AddCustomRow ( VariableCustomizationPrivate : : NodeTooltipText )
. EditCondition ( ! bIsRequired , nullptr )
. NameContent ( )
[
SNew ( STextBlock )
. Font ( IDetailLayoutBuilder : : GetDetailFontBold ( ) )
. Text ( VariableCustomizationPrivate : : NodeTooltipText )
]
. ValueContent ( )
[
SNew ( SMultiLineEditableTextBox )
. Text ( this , & FMetasoundInputDetailCustomization : : GetTooltip )
. OnTextCommitted ( this , & FMetasoundInputDetailCustomization : : OnTooltipCommitted )
. IsReadOnly ( bIsRequired )
. ModiferKeyForNewLine ( EModifierKey : : Shift )
. RevertTextOnEscape ( true )
. WrapTextAt ( VariableCustomizationPrivate : : DetailsTitleMaxWidth - VariableCustomizationPrivate : : DetailsTitleWrapPadding )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
] ;
AddDataTypeSelector ( DetailLayout , VariableCustomizationPrivate : : DataTypeNameText , GraphVariable , bIsRequired ) ;
CategoryBuilder . AddCustomRow ( LOCTEXT ( " InputPrivate " , " Private " ) )
. Visibility ( TAttribute < EVisibility > ( this , & FMetasoundInputDetailCustomization : : ExposePrivateVisibility ) )
. NameContent ( )
[
SNew ( STextBlock )
. Text ( LOCTEXT ( " InputPrivate " , " Private " ) )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
. ValueContent ( )
[
SNew ( SCheckBox )
. IsChecked ( this , & FMetasoundInputDetailCustomization : : OnGetPrivateCheckboxState )
. OnCheckStateChanged ( this , & FMetasoundInputDetailCustomization : : OnPrivateChanged )
] ;
FNodeHandle NodeHandle = GraphVariable - > GetNodeHandle ( ) ;
const TArray < FOutputHandle > & Outputs = NodeHandle - > GetOutputs ( ) ;
if ( ! ensure ( ! Outputs . IsEmpty ( ) ) )
{
return ;
}
IDetailCategoryBuilder & DefaultCategoryBuilder = DetailLayout . EditCategory ( " DefaultValue " ) ;
TSharedPtr < IPropertyHandle > LiteralHandle = DetailLayout . GetProperty ( GET_MEMBER_NAME_CHECKED ( UMetasoundEditorGraphInput , Literal ) ) ;
if ( ensure ( GraphVariable . IsValid ( ) ) & & ensure ( LiteralHandle . IsValid ( ) ) )
{
TSharedPtr < IPropertyHandle > DefaultValueHandle ;
UObject * LiteralObject = nullptr ;
if ( LiteralHandle - > GetValue ( LiteralObject ) = = FPropertyAccess : : Success )
{
if ( ensure ( LiteralObject ) )
{
LiteralHandle - > MarkHiddenByCustomization ( ) ;
if ( IDetailPropertyRow * Row = DefaultCategoryBuilder . AddExternalObjectProperty ( TArray < UObject * > ( { LiteralObject } ) , " Default " ) )
{
DefaultValueHandle = Row - > GetPropertyHandle ( ) ;
if ( DefaultValueHandle . IsValid ( ) )
{
SetDefaultPropertyMetaData ( DefaultValueHandle . ToSharedRef ( ) ) ;
FSimpleDelegate OnLiteralChanged = FSimpleDelegate : : CreateLambda ( [ GraphVariable = this - > GraphVariable ] ( )
{
if ( GraphVariable . IsValid ( ) )
{
GraphVariable - > OnLiteralChanged ( ) ;
}
} ) ;
DefaultValueHandle - > SetOnPropertyValueChanged ( OnLiteralChanged ) ;
DefaultValueHandle - > SetOnChildPropertyValueChanged ( OnLiteralChanged ) ;
TSharedPtr < IPropertyHandleArray > DefaultValueArray = DefaultValueHandle - > AsArray ( ) ;
if ( DefaultValueArray . IsValid ( ) )
{
DefaultValueArray - > SetOnNumElementsChanged ( OnLiteralChanged ) ;
}
}
}
}
else
{
DefaultCategoryBuilder . AddProperty ( LiteralHandle ) ;
}
}
}
}
void FMetasoundVariableDataTypeSelector : : OnDataTypeArrayChanged ( TWeakObjectPtr < UMetasoundEditorGraphVariable > InGraphVariable , ECheckBoxState InNewState )
{
if ( InGraphVariable . IsValid ( ) )
{
TSharedPtr < FString > DataTypeRoot = DataTypeComboBox - > GetSelectedItem ( ) ;
if ( ensure ( DataTypeRoot . IsValid ( ) ) )
{
FString DataTypeString = * DataTypeRoot . Get ( ) ;
if ( InNewState = = ECheckBoxState : : Checked )
{
DataTypeString + = VariableCustomizationPrivate : : ArrayIdentifier ;
}
2021-03-24 16:42:25 -04:00
// Have to stop playback to avoid attempting to change live edit data on invalid input type.
check ( GEditor ) ;
GEditor - > ResetPreviewAudioComponent ( ) ;
2021-03-19 15:10:57 -04:00
InGraphVariable - > SetDataType ( FName ( DataTypeString ) ) ;
// Required to rebuild the literal details customization.
// This is seemingly dangerous (as the Builder's raw ptr is cached),
// but the builder cannot be accessed any other way and instances of
// this type are always built from and managed by the parent DetailLayoutBuilder.
check ( DetailLayoutBuilder ) ;
DetailLayoutBuilder - > ForceRefreshDetails ( ) ;
}
}
}
void FMetasoundVariableDataTypeSelector : : OnBaseDataTypeChanged ( TWeakObjectPtr < UMetasoundEditorGraphVariable > InGraphVariable , TSharedPtr < FString > ItemSelected , ESelectInfo : : Type SelectInfo )
{
if ( ItemSelected . IsValid ( ) & & ! ItemSelected - > IsEmpty ( ) & & InGraphVariable . IsValid ( ) )
{
FString DataTypeString = * ItemSelected . Get ( ) ;
if ( DataTypeArrayCheckbox - > GetCheckedState ( ) = = ECheckBoxState : : Checked )
{
DataTypeString + = VariableCustomizationPrivate : : ArrayIdentifier ;
}
2021-03-24 16:42:25 -04:00
// Have to stop playback to avoid attempting to change live edit data on invalid input type.
check ( GEditor ) ;
GEditor - > ResetPreviewAudioComponent ( ) ;
2021-03-19 15:10:57 -04:00
InGraphVariable - > SetDataType ( FName ( DataTypeString ) ) ;
// Required to rebuild the literal details customization.
// This is seemingly dangerous (as the Builder's raw ptr is cached),
// but the builder cannot be accessed any other way and instances of
// this type are always built from and managed by the parent DetailLayoutBuilder.
check ( DetailLayoutBuilder ) ;
DetailLayoutBuilder - > ForceRefreshDetails ( ) ;
}
}
void FMetasoundInputDetailCustomization : : SetDefaultPropertyMetaData ( TSharedRef < IPropertyHandle > InDefaultPropertyHandle ) const
{
using namespace Frontend ;
if ( ! GraphVariable . IsValid ( ) )
{
return ;
}
FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) ;
if ( ! ensure ( Registry ) )
{
return ;
}
const FName TypeName = GetLiteralDataType ( ) ;
if ( TypeName . IsNone ( ) )
{
return ;
}
FString TypeNameString = TypeName . ToString ( ) ;
if ( TypeNameString . EndsWith ( VariableCustomizationPrivate : : ArrayIdentifier ) )
{
TypeNameString = TypeNameString . LeftChop ( VariableCustomizationPrivate : : ArrayIdentifier . Len ( ) ) ;
}
InDefaultPropertyHandle - > SetInstanceMetaData ( VariableCustomizationPrivate : : DataTypeNameIdentifier , TypeNameString ) ;
FDataTypeRegistryInfo DataTypeInfo ;
if ( ! ensure ( Registry - > GetInfoForDataType ( TypeName , DataTypeInfo ) ) )
{
return ;
}
const EMetasoundFrontendLiteralType LiteralType = GetMetasoundFrontendLiteralType ( DataTypeInfo . PreferredLiteralType ) ;
if ( LiteralType ! = EMetasoundFrontendLiteralType : : UObject & & LiteralType ! = EMetasoundFrontendLiteralType : : UObjectArray )
{
return ;
}
UClass * ProxyGenClass = DataTypeInfo . ProxyGeneratorClass ;
if ( ProxyGenClass )
{
const FString ClassName = ProxyGenClass - > GetName ( ) ;
InDefaultPropertyHandle - > SetInstanceMetaData ( VariableCustomizationPrivate : : ProxyGeneratorClassNameIdentifier , ClassName ) ;
}
}
FName FMetasoundInputDetailCustomization : : GetLiteralDataType ( ) const
{
using namespace Frontend ;
2021-03-23 17:55:31 -04:00
FName TypeName ;
2021-03-19 15:10:57 -04:00
2021-03-23 17:55:31 -04:00
// Just take last type. If more than one, all types are the same.
FConstNodeHandle NodeHandle = GraphVariable - > GetConstNodeHandle ( ) ;
NodeHandle - > IterateConstOutputs ( [ InTypeName = & TypeName ] ( FConstOutputHandle OutputHandle )
2021-03-19 15:10:57 -04:00
{
2021-03-23 17:55:31 -04:00
* InTypeName = OutputHandle - > GetDataType ( ) ;
} ) ;
2021-03-19 15:10:57 -04:00
2021-03-23 17:55:31 -04:00
return TypeName ;
2021-03-19 15:10:57 -04:00
}
void FMetasoundInputDetailCustomization : : OnDisplayNameChanged ( const FText & InNewName )
{
using namespace Frontend ;
bIsNameInvalid = false ;
2021-03-23 17:55:31 -04:00
DisplayNameEditableTextBox - > SetError ( FText : : GetEmpty ( ) ) ;
2021-03-19 15:10:57 -04:00
if ( ! ensure ( GraphVariable . IsValid ( ) ) )
{
return ;
}
if ( InNewName . IsEmpty ( ) )
{
bIsNameInvalid = true ;
DisplayNameEditableTextBox - > SetError ( FText : : Format ( LOCTEXT ( " InputRenameInvalid_NameEmpty " , " {0} cannot be empty string. " ) , InNewName ) ) ;
return ;
}
FConstNodeHandle NodeHandle = GraphVariable - > GetConstNodeHandle ( ) ;
FConstGraphHandle GraphHandle = NodeHandle - > GetOwningGraph ( ) ;
const FGuid NodeID = NodeHandle - > GetID ( ) ;
GraphHandle - > IterateConstNodes ( [ this , NodeID , InNewName ] ( FConstNodeHandle NodeToCompare )
{
2021-03-23 17:55:31 -04:00
// Disregard display name collisions with hidden nodes
const bool bIsVisible = NodeToCompare - > GetNodeStyle ( ) . Display . Visibility = = EMetasoundFrontendNodeStyleDisplayVisibility : : Visible ;
if ( NodeID ! = NodeToCompare - > GetID ( ) & & bIsVisible )
2021-03-19 15:10:57 -04:00
{
if ( InNewName . CompareToCaseIgnored ( NodeToCompare - > GetDisplayName ( ) ) = = 0 )
{
bIsNameInvalid = true ;
DisplayNameEditableTextBox - > SetError ( FText : : Format ( LOCTEXT ( " InputRenameInvalid_NameTaken " , " {0} is already in use " ) , InNewName ) ) ;
}
}
} , EMetasoundFrontendClassType : : Input ) ;
}
void FMetasoundOutputDetailCustomization : : CustomizeDetails ( IDetailLayoutBuilder & DetailLayout )
{
using namespace Frontend ;
TMetasoundVariableDetailCustomization < UMetasoundEditorGraphOutput > : : CustomizeDetails ( DetailLayout ) ;
if ( ! GraphVariable . IsValid ( ) )
{
return ;
}
IDetailCategoryBuilder & CategoryBuilder = DetailLayout . EditCategory ( " General " ) ;
const bool bIsRequired = IsRequired ( ) ;
DisplayNameEditableTextBox = SNew ( SEditableTextBox )
. Text ( this , & FMetasoundOutputDetailCustomization : : GetDisplayName )
. OnTextChanged ( this , & FMetasoundOutputDetailCustomization : : OnDisplayNameChanged )
. OnTextCommitted ( this , & FMetasoundOutputDetailCustomization : : OnDisplayNameCommitted )
. IsReadOnly ( bIsRequired )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) ) ;
CategoryBuilder . AddCustomRow ( VariableCustomizationPrivate : : OutputNameText )
. EditCondition ( ! bIsRequired , nullptr )
. NameContent ( )
[
SNew ( STextBlock )
. Font ( IDetailLayoutBuilder : : GetDetailFontBold ( ) )
. Text ( VariableCustomizationPrivate : : OutputNameText )
. ToolTipText ( TAttribute < FText > : : Create ( [ GraphVariable = this - > GraphVariable ] ( )
{
if ( GraphVariable . IsValid ( ) )
{
FNodeHandle NodeHandle = GraphVariable - > GetNodeHandle ( ) ;
return NodeHandle - > GetDescription ( ) ;
}
return FText : : GetEmpty ( ) ;
} ) )
]
. ValueContent ( )
[
DisplayNameEditableTextBox . ToSharedRef ( )
] ;
CategoryBuilder . AddCustomRow ( VariableCustomizationPrivate : : NodeTooltipText )
. EditCondition ( ! bIsRequired , nullptr )
. NameContent ( )
[
SNew ( STextBlock )
. Font ( IDetailLayoutBuilder : : GetDetailFontBold ( ) )
. Text ( VariableCustomizationPrivate : : NodeTooltipText )
]
. ValueContent ( )
[
SNew ( SMultiLineEditableTextBox )
. Text ( this , & FMetasoundOutputDetailCustomization : : GetTooltip )
. OnTextCommitted ( this , & FMetasoundOutputDetailCustomization : : OnTooltipCommitted )
. IsReadOnly ( bIsRequired )
. ModiferKeyForNewLine ( EModifierKey : : Shift )
. RevertTextOnEscape ( true )
. WrapTextAt ( VariableCustomizationPrivate : : DetailsTitleMaxWidth - VariableCustomizationPrivate : : DetailsTitleWrapPadding )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
] ;
AddDataTypeSelector ( DetailLayout , VariableCustomizationPrivate : : DataTypeNameText , GraphVariable , bIsRequired ) ;
CategoryBuilder . AddCustomRow ( LOCTEXT ( " OutputPrivate " , " Private " ) )
. Visibility ( TAttribute < EVisibility > ( this , & FMetasoundOutputDetailCustomization : : ExposePrivateVisibility ) )
. NameContent ( )
[
SNew ( STextBlock )
. Text ( LOCTEXT ( " OutputPrivate " , " Private " ) )
. Font ( IDetailLayoutBuilder : : GetDetailFont ( ) )
]
. ValueContent ( )
[
SNew ( SCheckBox )
. IsChecked ( this , & FMetasoundOutputDetailCustomization : : OnGetPrivateCheckboxState )
. OnCheckStateChanged ( this , & FMetasoundOutputDetailCustomization : : OnPrivateChanged )
] ;
}
void FMetasoundOutputDetailCustomization : : SetDefaultPropertyMetaData ( TSharedRef < IPropertyHandle > InDefaultPropertyHandle ) const
{
using namespace Frontend ;
if ( ! GraphVariable . IsValid ( ) )
{
return ;
}
FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) ;
if ( ! ensure ( Registry ) )
{
return ;
}
const FName TypeName = GetLiteralDataType ( ) ;
if ( TypeName . IsNone ( ) )
{
return ;
}
FString TypeNameString = TypeName . ToString ( ) ;
2021-03-23 17:55:31 -04:00
if ( TypeNameString . EndsWith ( VariableCustomizationPrivate : : ArrayIdentifier ) )
2021-03-19 15:10:57 -04:00
{
2021-03-23 17:55:31 -04:00
TypeNameString = TypeNameString . LeftChop ( VariableCustomizationPrivate : : ArrayIdentifier . Len ( ) ) ;
2021-03-19 15:10:57 -04:00
}
InDefaultPropertyHandle - > SetInstanceMetaData ( VariableCustomizationPrivate : : DataTypeNameIdentifier , TypeNameString ) ;
FDataTypeRegistryInfo DataTypeInfo ;
if ( ! ensure ( Registry - > GetInfoForDataType ( TypeName , DataTypeInfo ) ) )
{
return ;
}
const EMetasoundFrontendLiteralType LiteralType = GetMetasoundFrontendLiteralType ( DataTypeInfo . PreferredLiteralType ) ;
if ( LiteralType ! = EMetasoundFrontendLiteralType : : UObject & & LiteralType ! = EMetasoundFrontendLiteralType : : UObjectArray )
{
return ;
}
UClass * ProxyGenClass = DataTypeInfo . ProxyGeneratorClass ;
if ( ProxyGenClass )
{
const FString ClassName = ProxyGenClass - > GetName ( ) ;
InDefaultPropertyHandle - > SetInstanceMetaData ( VariableCustomizationPrivate : : ProxyGeneratorClassNameIdentifier , ClassName ) ;
}
}
FName FMetasoundOutputDetailCustomization : : GetLiteralDataType ( ) const
{
using namespace Frontend ;
2021-03-23 17:55:31 -04:00
FName TypeName ;
2021-03-19 15:10:57 -04:00
2021-03-23 17:55:31 -04:00
// Just take last type. If more than one, all types are the same.
FConstNodeHandle NodeHandle = GraphVariable - > GetConstNodeHandle ( ) ;
NodeHandle - > IterateConstInputs ( [ InTypeName = & TypeName ] ( FConstInputHandle InputHandle )
2021-03-19 15:10:57 -04:00
{
2021-03-23 17:55:31 -04:00
* InTypeName = InputHandle - > GetDataType ( ) ;
} ) ;
2021-03-19 15:10:57 -04:00
2021-03-23 17:55:31 -04:00
return TypeName ;
2021-03-19 15:10:57 -04:00
}
void FMetasoundOutputDetailCustomization : : OnDisplayNameChanged ( const FText & InNewName )
{
using namespace Frontend ;
bIsNameInvalid = false ;
2021-03-23 17:55:31 -04:00
DisplayNameEditableTextBox - > SetError ( FText : : GetEmpty ( ) ) ;
2021-03-19 15:10:57 -04:00
if ( ! ensure ( GraphVariable . IsValid ( ) ) )
{
return ;
}
if ( InNewName . IsEmpty ( ) )
{
bIsNameInvalid = true ;
DisplayNameEditableTextBox - > SetError ( FText : : Format ( LOCTEXT ( " OutputRenameInvalid_NameEmpty " , " {0} cannot be empty string. " ) , InNewName ) ) ;
return ;
}
FConstNodeHandle NodeHandle = GraphVariable - > GetConstNodeHandle ( ) ;
FConstGraphHandle GraphHandle = NodeHandle - > GetOwningGraph ( ) ;
const FGuid NodeID = NodeHandle - > GetID ( ) ;
GraphHandle - > IterateConstNodes ( [ this , NodeID , InNewName ] ( FConstNodeHandle NodeToCompare )
{
2021-04-03 01:40:58 -04:00
if ( NodeID ! = NodeToCompare - > GetID ( ) )
2021-03-19 15:10:57 -04:00
{
if ( InNewName . CompareToCaseIgnored ( NodeToCompare - > GetDisplayName ( ) ) = = 0 )
{
bIsNameInvalid = true ;
DisplayNameEditableTextBox - > SetError ( FText : : Format ( LOCTEXT ( " OutputRenameInvalid_NameTaken " , " {0} is already in use " ) , InNewName ) ) ;
}
}
} , EMetasoundFrontendClassType : : Output ) ;
}
} // namespace Editor
} // namespace Metasound
# undef LOCTEXT_NAMESPACE