Files
UnrealEngineUWP/Engine/Plugins/Runtime/Metasound/Source/MetasoundEditor/Private/MetasoundDetailCustomization.cpp
Rob Gay a7281c3c12 Literal Input Nodes
GainMultiply fix-ups (fade fast to avoid stair stepping & missing input declaration)
- Various EdGraphBuilder clean-up
#rb phil.popp
#jira UEAU-638

[CL 15172255 by Rob Gay in ue5-main branch]
2021-01-23 12:59:01 -04:00

330 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetasoundDetailCustomization.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 "IDetailGroup.h"
#include "MetasoundAssetBase.h"
#include "MetasoundFrontend.h"
#include "MetasoundFrontendController.h"
#include "MetasoundUObjectRegistry.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"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/Text/STextBlock.h"
#define LOCTEXT_NAMESPACE "MetasoundEditor"
static int32 ShowLiteralMetasoundInputsInEditorCVar = 0;
FAutoConsoleVariableRef CVarShowLiteralMetasoundInputsInEditor(
TEXT("au.Debug.Editor.Metasounds.ShowLiteralInputs"),
ShowLiteralMetasoundInputsInEditorCVar,
TEXT("Show literal inputs in the Metasound Editor.\n")
TEXT("0: Disabled (default), !0: Enabled"),
ECVF_Default);
namespace Metasound
{
namespace Editor
{
FName BuildChildPath(const FString& InBasePath, FName InPropertyName)
{
return FName(InBasePath + TEXT(".") + InPropertyName.ToString());
}
FName BuildChildPath(const FName& InBasePath, FName InPropertyName)
{
return FName(InBasePath.ToString() + TEXT(".") + InPropertyName.ToString());
}
TSet<FString> GetLiteralInputs(IDetailLayoutBuilder& InDetailLayout)
{
TSet<FString> LiteralInputs;
TArray<TWeakObjectPtr<UObject>> Objects;
InDetailLayout.GetObjectsBeingCustomized(Objects);
if (Objects.IsEmpty() || !Objects[0].IsValid())
{
return LiteralInputs;
}
UObject* Metasound = Objects[0].Get();
if (FMetasoundAssetBase* MetasoundAsset = IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(Metasound))
{
Frontend::FGraphHandle GraphHandle = MetasoundAsset->GetRootGraphHandle();
TArray<Frontend::FNodeHandle> InputNodes = GraphHandle->GetInputNodes();
for (Frontend::FNodeHandle& NodeHandle : InputNodes)
{
if (NodeHandle->GetNodeStyle().Display.Visibility == EMetasoundFrontendNodeStyleDisplayVisibility::Hidden)
{
LiteralInputs.Add(NodeHandle->GetNodeName());
}
}
}
return LiteralInputs;
}
template <typename T>
void BuildIOFixedArray(IDetailLayoutBuilder& InDetailLayout, FName InCategoryName, FName InPropertyName, const TSet<FString>& InRequiredValues)
{
const bool bIsInput = InCategoryName == "Inputs";
IDetailCategoryBuilder& CategoryBuilder = InDetailLayout.EditCategory(InCategoryName);
TSharedPtr<IPropertyHandle> ParentProperty = InDetailLayout.GetProperty(InPropertyName);
TSharedPtr<IPropertyHandleArray> ArrayHandle = ParentProperty->AsArray();
TSet<FString> LiteralInputs;
if (bIsInput && !ShowLiteralMetasoundInputsInEditorCVar)
{
LiteralInputs = GetLiteralInputs(InDetailLayout);
}
uint32 NumElements = 0;
ArrayHandle->GetNumElements(NumElements);
for (int32 i = 0; i < static_cast<int32>(NumElements); ++i)
{
TSharedRef<IPropertyHandle> ArrayItemHandle = ArrayHandle->GetElement(i);
const FName TypeNamePropertyName = GET_MEMBER_NAME_CHECKED(T, TypeName);
const FName NamePropertyName = GET_MEMBER_NAME_CHECKED(T, Name);
const FName ToolTipPropertyName = GET_MEMBER_NAME_CHECKED(FMetasoundFrontendVertexMetadata, Description);
const FName DisplayNamePropertyName = GET_MEMBER_NAME_CHECKED(FMetasoundFrontendVertexMetadata, DisplayName);
TSharedPtr<IPropertyHandle> TypeProperty = ArrayItemHandle->GetChildHandle(TypeNamePropertyName);
TSharedPtr<IPropertyHandle> NameProperty = ArrayItemHandle->GetChildHandle(NamePropertyName);
TSharedPtr<IPropertyHandle> ToolTipProperty = ArrayItemHandle->GetChildHandle(ToolTipPropertyName, true /* bRecurse */);
TSharedPtr<IPropertyHandle> DisplayNameProperty = ArrayItemHandle->GetChildHandle(DisplayNamePropertyName, true /* bRecurse */);
FString Name;
const bool bNameFound = NameProperty->GetValue(Name) == FPropertyAccess::Success;
const bool bIsRequired = bNameFound && InRequiredValues.Contains(Name);
// Hide literal members
if (LiteralInputs.Contains(Name))
{
continue;
}
FText DisplayName;
DisplayNameProperty->GetValue(DisplayName);
DisplayNameProperty->SetInstanceMetaData("LastValidName", DisplayName.ToString());
FSimpleDelegate NameChangeDelegate = FSimpleDelegate::CreateLambda([ChangedIndex = i, DisplayNameProperty, ArrayHandle, DisplayNamePropertyName]
{
FText MatchDisplayName;
if (DisplayNameProperty->GetValue(MatchDisplayName) != FPropertyAccess::Success)
{
return;
}
if (const FString* LastValidName = DisplayNameProperty->GetInstanceMetaData("LastValidName"))
{
uint32 NumElements = 0;
ArrayHandle->GetNumElements(NumElements);
for (int32 Elem = 0; Elem < static_cast<int32>(NumElements); ++Elem)
{
TSharedRef<IPropertyHandle> ElemHandle = ArrayHandle->GetElement(Elem);
TSharedPtr<IPropertyHandle> ElemNameProperty = ElemHandle->GetChildHandle(DisplayNamePropertyName, true /* bRecurse */);
FText ElemDisplayName;
if (ElemNameProperty->GetValue(ElemDisplayName) == FPropertyAccess::Success)
{
if (Elem != ChangedIndex && !ElemDisplayName.CompareTo(MatchDisplayName))
{
DisplayNameProperty->SetValue(FText::FromString(*LastValidName));
FNotificationInfo Info(LOCTEXT("MetasoundEditor_InvalidRename", "Rename failed: Input/output with name already exists."));
Info.bFireAndForget = true;
Info.ExpireDuration = 2.0f;
Info.bUseThrobber = true;
FSlateNotificationManager::Get().AddNotification(Info);
return;
}
}
}
}
DisplayNameProperty->SetInstanceMetaData("LastValidName", MatchDisplayName.ToString());
});
DisplayNameProperty->SetOnPropertyValueChanged(NameChangeDelegate);
CategoryBuilder.AddCustomRow(ParentProperty->GetPropertyDisplayName())
.EditCondition(!bIsRequired, nullptr)
.NameContent()
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFontBold())
.Text(TAttribute<FText>::Create([i, bIsRequired, DisplayNameProperty, TypeProperty]()
{
FName TypeName;
TypeProperty->GetValue(TypeName);
FString TypeNameString = TypeName.ToString();
// Remove namespace info to keep concise
TypeNameString.RightChopInline(TypeNameString.Find(TEXT(":"), ESearchCase::IgnoreCase, ESearchDir::FromEnd) + 1);
FText DisplayName;
DisplayNameProperty->GetValue(DisplayName);
if (bIsRequired)
{
return FText::Format(LOCTEXT("MetasoundEditor_FixedIOArrayRequiredEntry_Format", "{0}. {1} ({2}, Required)"), FText::AsNumber(i + 1), DisplayName, FText::FromString(TypeNameString));
}
else
{
return FText::Format(LOCTEXT("MetasoundEditor_FixedIOArray_Format", "{0}. {1} ({2})"), FText::AsNumber(i + 1), DisplayName, FText::FromString(TypeNameString));
}
}))
.ToolTipText(TAttribute<FText>::Create([ToolTipProperty]()
{
FText ToolTip;
ToolTipProperty->GetValue(ToolTip);
return ToolTip;
}))
];
if (!bIsRequired)
{
CategoryBuilder.AddProperty(DisplayNameProperty);
CategoryBuilder.AddProperty(ToolTipProperty);
}
if (bIsInput)
{
const FName DefaultsPropertyName = GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassInput, Defaults);
TSharedPtr<IPropertyHandle> DefaultsProperty = ArrayItemHandle->GetChildHandle(DefaultsPropertyName);
TSharedPtr<IPropertyHandleArray> DefaultsArrayHandle = DefaultsProperty->AsArray();
uint32 NumDefaults = 0;
DefaultsArrayHandle->GetNumElements(NumDefaults);
for (uint32 InputLiteralIndex = 0; InputLiteralIndex < NumDefaults; ++InputLiteralIndex)
{
TSharedPtr<IPropertyHandle> LiteralHandle = DefaultsArrayHandle->GetElement(InputLiteralIndex)->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMetasoundFrontendVertexLiteral, Value));
CategoryBuilder.AddProperty(LiteralHandle);
}
}
}
FSimpleDelegate RefreshDelegate = FSimpleDelegate::CreateLambda([DetailLayout = &InDetailLayout]()
{
DetailLayout->ForceRefreshDetails();
});
ArrayHandle->SetOnNumElementsChanged(RefreshDelegate);
};
FMetasoundDetailCustomization::FMetasoundDetailCustomization(FName InDocumentPropertyName)
: IDetailCustomization()
, DocumentPropertyName(InDocumentPropertyName)
{
}
FName FMetasoundDetailCustomization::GetMetadataRootClassPath() const
{
return Metasound::Editor::BuildChildPath(DocumentPropertyName, GET_MEMBER_NAME_CHECKED(FMetasoundFrontendDocument, RootGraph));
}
FName FMetasoundDetailCustomization::GetMetadataPropertyPath() const
{
const FName RootClass = FName(GetMetadataRootClassPath());
return Metasound::Editor::BuildChildPath(RootClass, GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClass, Metadata));
}
void FMetasoundDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
using namespace Metasound::Editor;
// General Category
IDetailCategoryBuilder& GeneralCategoryBuilder = DetailLayout.EditCategory("General");
TArray<TWeakObjectPtr<UObject>> ObjectsCustomized;
DetailLayout.GetObjectsBeingCustomized(ObjectsCustomized);
const FName AuthorPropertyPath = BuildChildPath(GetMetadataPropertyPath(), GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassMetadata, Author));
const FName DescPropertyPath = BuildChildPath(GetMetadataPropertyPath(), GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassMetadata, Description));
const FName NodeTypePropertyPath = BuildChildPath(GetMetadataPropertyPath(), GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassMetadata, Type));
const FName VersionPropertyPath = BuildChildPath(GetMetadataPropertyPath(), GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassMetadata, Version));
const FName MajorVersionPropertyPath = BuildChildPath(VersionPropertyPath, GET_MEMBER_NAME_CHECKED(FMetasoundFrontendVersionNumber, Major));
const FName MinorVersionPropertyPath = BuildChildPath(VersionPropertyPath, GET_MEMBER_NAME_CHECKED(FMetasoundFrontendVersionNumber, Minor));
TSharedPtr<IPropertyHandle> AuthorHandle = DetailLayout.GetProperty(AuthorPropertyPath);
TSharedPtr<IPropertyHandle> DescHandle = DetailLayout.GetProperty(DescPropertyPath);
TSharedPtr<IPropertyHandle> NodeTypeHandle = DetailLayout.GetProperty(NodeTypePropertyPath);
TSharedPtr<IPropertyHandle> MajorVersionHandle = DetailLayout.GetProperty(MajorVersionPropertyPath);
TSharedPtr<IPropertyHandle> MinorVersionHandle = DetailLayout.GetProperty(MinorVersionPropertyPath);
GeneralCategoryBuilder.AddProperty(NodeTypeHandle);
GeneralCategoryBuilder.AddProperty(AuthorHandle);
GeneralCategoryBuilder.AddProperty(DescHandle);
GeneralCategoryBuilder.AddProperty(MajorVersionHandle);
GeneralCategoryBuilder.AddProperty(MinorVersionHandle);
// Input/Output Categories
// If editing multiple metasound objects, all should be the same type, so safe to just check first in array for
// required inputs/outputs
TArray<TWeakObjectPtr<UObject>> Objects;
TSet<FString> RequiredInputs;
TSet<FString> RequiredOutputs;
DetailLayout.GetObjectsBeingCustomized(Objects);
if (Objects.Num() > 0)
{
FMetasoundAssetBase* MetasoundAsset = IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(Objects[0].Get());
check(MetasoundAsset);
Frontend::FDocumentHandle DocumentHandle = MetasoundAsset->GetDocumentHandle();
for (const FMetasoundFrontendClassVertex& Desc : DocumentHandle->GetRequiredInputs())
{
RequiredInputs.Add(Desc.Name);
}
for (const FMetasoundFrontendClassVertex& Desc : DocumentHandle->GetRequiredOutputs())
{
RequiredOutputs.Add(Desc.Name);
}
}
const FName InterfacePropertyPath = BuildChildPath(GetMetadataRootClassPath(), GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClass, Interface));
const FName InputsPropertyPath = BuildChildPath(InterfacePropertyPath, GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassInterface, Inputs));
const FName OutputsPropertyPath = BuildChildPath(InterfacePropertyPath, GET_MEMBER_NAME_CHECKED(FMetasoundFrontendClassInterface, Outputs));
BuildIOFixedArray<FMetasoundFrontendClassInput>(DetailLayout, "Inputs", InputsPropertyPath, RequiredInputs);
BuildIOFixedArray<FMetasoundFrontendClassOutput>(DetailLayout, "Outputs", OutputsPropertyPath, RequiredOutputs);
// Hack to hide parent structs for nested metadata properties
DetailLayout.HideCategory("CustomView");
// Hack to hide categories brought in from UMetasoundSource inherited from USoundBase
DetailLayout.HideCategory("Analysis");
DetailLayout.HideCategory("Attenuation");
DetailLayout.HideCategory("Curves");
DetailLayout.HideCategory("Debug");
DetailLayout.HideCategory("Developer");
DetailLayout.HideCategory("Effects");
DetailLayout.HideCategory("File Path");
DetailLayout.HideCategory("Format");
DetailLayout.HideCategory("Info");
DetailLayout.HideCategory("Loading");
DetailLayout.HideCategory("Modulation");
DetailLayout.HideCategory("Playback");
DetailLayout.HideCategory("Sound");
DetailLayout.HideCategory("SoundWave");
DetailLayout.HideCategory("Subtitles");
DetailLayout.HideCategory("Voice Management");
}
} // namespace Editor
} // namespace Metasound
#undef LOCTEXT_NAMESPACE