// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundDefaultLiteralCustomization.h" #include "DetailLayoutBuilder.h" #include "MetasoundDetailCustomization.h" #include "MetasoundEditor.h" #include "MetasoundEditorGraph.h" #include "MetasoundEditorGraphBuilder.h" #include "MetasoundEditorGraphMemberDefaults.h" #include "MetasoundNodeDetailCustomization.h" #include "MetasoundSettings.h" #include "PropertyCustomizationHelpers.h" #include "SSearchableComboBox.h" #include "UObject/WeakObjectPtrTemplates.h" #define LOCTEXT_NAMESPACE "MetaSoundEditor" namespace Metasound::Editor { namespace LiteralCustomizationPrivate { FGuid GetGuidPropertyValue(TSharedPtr GuidProperty) { // Randomized so if property be invalid for whatever reason, // the page default entry isn't mistakenly removed. FGuid Guid = FGuid::NewGuid(); if (!ensure(GuidProperty.IsValid())) { return Guid; } auto GetGuidUInt = [&GuidProperty](FName Section) { int32 Value = 0; TSharedPtr SectionHandle = GuidProperty->GetChildHandle(Section); if (ensure(SectionHandle.IsValid())) { SectionHandle->GetValue(Value); } return Value; }; return FGuid( GetGuidUInt(GET_MEMBER_NAME_CHECKED(FGuid, A)), GetGuidUInt(GET_MEMBER_NAME_CHECKED(FGuid, B)), GetGuidUInt(GET_MEMBER_NAME_CHECKED(FGuid, C)), GetGuidUInt(GET_MEMBER_NAME_CHECKED(FGuid, D)) ); } } // namespace LiteralCustomizationPrivate FMetasoundDefaultLiteralCustomizationBase::FMetasoundDefaultLiteralCustomizationBase(IDetailCategoryBuilder& InDefaultCategoryBuilder) : DefaultCategoryBuilder(&InDefaultCategoryBuilder) { } FMetasoundDefaultLiteralCustomizationBase::~FMetasoundDefaultLiteralCustomizationBase() { if (UMetaSoundSettings* Settings = GetMutableDefault()) { if (OnPageSettingsUpdatedHandle.IsValid()) { Settings->GetOnPageSettingsUpdatedDelegate().Remove(OnPageSettingsUpdatedHandle); } } } void FMetasoundDefaultLiteralCustomizationBase::BuildPageDefaultComboBox(UMetasoundEditorGraphMemberDefaultLiteral& Literal, FText RowName) { using namespace Engine; check(DefaultCategoryBuilder); TWeakObjectPtr LiteralPtr(&Literal); if (UMetaSoundSettings* Settings = GetMutableDefault()) { FOnPageSettingsUpdated& UpdatedDelegate = Settings->GetOnPageSettingsUpdatedDelegate(); if (OnPageSettingsUpdatedHandle.IsValid()) { UpdatedDelegate.Remove(OnPageSettingsUpdatedHandle); } OnPageSettingsUpdatedHandle = UpdatedDelegate.AddLambda([this, LiteralPtr]() { UpdatePagePickerNames(LiteralPtr); if (PageDefaultComboBox.IsValid()) { PageDefaultComboBox->RefreshOptions(); } }); } else { return; } SAssignNew(PageDefaultComboBox, SSearchableComboBox) .OptionsSource(&AddablePageStringNames) .OnGenerateWidget_Lambda([](TSharedPtr InItem) { return SNew(STextBlock).Text(FText::FromString(*InItem)); }) .OnSelectionChanged_Lambda([this, LiteralPtr](TSharedPtr NameToAdd, ESelectInfo::Type InSelectInfo) { using namespace Engine; using namespace Frontend; if (InSelectInfo != ESelectInfo::OnNavigation) { if (!LiteralPtr.IsValid()) { return; } UMetasoundEditorGraphMember* Member = LiteralPtr->FindMember(); if (!Member) { return; } if (!NameToAdd.IsValid()) { return; } const UMetaSoundSettings* Settings = GetDefault(); check(Settings); if (const FMetaSoundPageSettings* PageSettings = Settings->FindPageSettings(FName(*NameToAdd))) { const FScopedTransaction Transaction(FText::Format(LOCTEXT("AddPageDefaultValueTransactionFormat", "Add '{0}' Page '{1}' Default Value"), FText::FromName(Member->GetMemberName()), FText::FromString(*NameToAdd))); FMetaSoundFrontendDocumentBuilder& Builder = Member->GetFrontendBuilderChecked(); UObject& MetaSound = Builder.CastDocumentObjectChecked(); MetaSound.Modify(); LiteralPtr->Modify(); LiteralPtr->InitDefault(PageSettings->UniqueId); constexpr bool bPostTransaction = false; Member->UpdateFrontendDefaultLiteral(bPostTransaction, &PageSettings->UniqueId); FMetasoundAssetBase& MetasoundAsset = Editor::FGraphBuilder::GetOutermostMetaSoundChecked(*LiteralPtr); MetasoundAsset.GetModifyContext().AddMemberIDsModified({ Member->GetMemberID() }); UpdatePagePickerNames(LiteralPtr); PageDefaultComboBox->RefreshOptions(); FGraphBuilder::RegisterGraphWithFrontend(MetaSound); } } }) .Content() [ SNew(STextBlock) .Text(LOCTEXT("AddPageDefaultValuePrompt", "Add Page Default Value...")) ]; DefaultCategoryBuilder->AddCustomRow(RowName) .IsEnabled(GetEnabled()) .ValueContent() [ SNew(SHorizontalBox) .Visibility(Visibility) + SHorizontalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) .AutoWidth() [ PageDefaultComboBox->AsShared() ] + SHorizontalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) .AutoWidth() [ PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda([this, LiteralPtr]() { using namespace Frontend; if (!LiteralPtr.IsValid()) { return; } UMetasoundEditorGraphMember* Member = LiteralPtr->FindMember(); if (!Member) { return; } const FScopedTransaction Transaction(LOCTEXT("ResetPageDefaultsTransaction", "Reset Paged Defaults")); const UMetaSoundSettings* Settings = GetDefault(); check(Settings); FMetaSoundFrontendDocumentBuilder& Builder = Member->GetFrontendBuilderChecked(); UObject& MetaSound = Builder.CastDocumentObjectChecked(); MetaSound.Modify(); LiteralPtr->Modify(); LiteralPtr->ResetDefaults(); constexpr bool bPostTransaction = false; Member->UpdateFrontendDefaultLiteral(bPostTransaction); FMetasoundAssetBase& MetasoundAsset = Editor::FGraphBuilder::GetOutermostMetaSoundChecked(*LiteralPtr); MetasoundAsset.GetModifyContext().AddMemberIDsModified({ Member->GetMemberID() }); UpdatePagePickerNames(LiteralPtr); PageDefaultComboBox->RefreshOptions(); FGraphBuilder::RegisterGraphWithFrontend(MetaSound); }), LOCTEXT("ResetPageDefaultsTooltip", "Resets page defaults for the given member, leaving just the initial value for the required 'Default' page.")) ] ]; } TSharedRef FMetasoundDefaultLiteralCustomizationBase::BuildPageDefaultNameWidget(UMetasoundEditorGraphMemberDefaultLiteral& Literal, TSharedRef ElementProperty) { TSharedPtr PageNameProperty = ElementProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMetasoundEditorMemberPageDefault, PageName)); if (!ensure(PageNameProperty.IsValid())) { return SNullWidget::NullWidget; } TSharedRef NameBox = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2, 0) [ PageNameProperty->CreatePropertyValueWidget() ]; const UMetasoundEditorGraphMember* Member = Literal.FindMember(); if (!ensure(Member)) { return SNullWidget::NullWidget; } // Can't delete the default page, so only show remove page field if non-default/project defined page. const FGuid PageID = LiteralCustomizationPrivate::GetGuidPropertyValue(ElementProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMetasoundEditorMemberPageDefault, PageID))); if (PageID != Frontend::DefaultPageID) { TWeakObjectPtr LiteralPtr(&Literal); FName PageName; PageNameProperty->GetValue(PageName); const FText RemoveDescription = FText::Format(LOCTEXT("RemovePageDefaultTransactionFormat", "Remove '{0}' Page '{1}' Default Value"), FText::FromName(Member->GetMemberName()), FText::FromName(PageName)); TSharedRef RemovePageDefaultButton = PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda([this, RemoveDescription, PageID, LiteralPtr]() { using namespace Frontend; if (!LiteralPtr.IsValid()) { return; } UMetasoundEditorGraphMember* Member = LiteralPtr->FindMember(); if (!Member) { return; } const FScopedTransaction Transaction(RemoveDescription); const UMetaSoundSettings* Settings = GetDefault(); check(Settings); FMetaSoundFrontendDocumentBuilder& Builder = Member->GetFrontendBuilderChecked(); UObject& MetaSound = Builder.CastDocumentObjectChecked(); MetaSound.Modify(); LiteralPtr->Modify(); LiteralPtr->RemoveDefault(PageID); constexpr bool bPostTransaction = false; Member->UpdateFrontendDefaultLiteral(bPostTransaction); FMetasoundAssetBase& MetasoundAsset = Editor::FGraphBuilder::GetOutermostMetaSoundChecked(*LiteralPtr); MetasoundAsset.GetModifyContext().AddMemberIDsModified({ Member->GetMemberID() }); UpdatePagePickerNames(LiteralPtr); PageDefaultComboBox->RefreshOptions(); FGraphBuilder::RegisterGraphWithFrontend(MetaSound); }), RemoveDescription); NameBox->AddSlot() .FillWidth(1) .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(2, 0) [ RemovePageDefaultButton ]; } if (Member->IsDefaultPaged()) { TAttribute ExecVisibility = TAttribute::CreateLambda([MemberPtr = TWeakObjectPtr(Member), PageID]() { if (const UMetasoundEditorGraphMember* GraphMember = MemberPtr.Get()) { const FMetaSoundFrontendDocumentBuilder& Builder = GraphMember->GetFrontendBuilderChecked(); if (const FMetasoundFrontendClassInput* ClassInput = Builder.FindGraphInput(GraphMember->GetMemberName())) { const bool bIsPreviewing = Editor::IsPreviewingPageInputDefault(Builder, *ClassInput, PageID); return bIsPreviewing ? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Collapsed; } return EVisibility::Collapsed; }); TSharedRef ExecImageWidget = SNew(SImage) .Image(Style::CreateSlateIcon("MetasoundEditor.Page.Executing").GetIcon()) .ColorAndOpacity(Style::GetPageExecutingColor()) .Visibility(MoveTemp(ExecVisibility)); NameBox->AddSlot() .FillWidth(1) .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(2, 0) [ ExecImageWidget ]; } return NameBox; } void FMetasoundDefaultLiteralCustomizationBase::CustomizeDefaults(UMetasoundEditorGraphMemberDefaultLiteral& InLiteral, IDetailLayoutBuilder& InDetailLayout) { CustomizePageDefaultRows(InLiteral, InDetailLayout); } void FMetasoundDefaultLiteralCustomizationBase::CustomizePageDefaultRows(UMetasoundEditorGraphMemberDefaultLiteral& InLiteral, IDetailLayoutBuilder& InDetailLayout) { DefaultProperties.Reset(); const UMetasoundEditorGraphMember* Member = InLiteral.FindMember(); if (!Member) { return; } const bool bIsPagedDefault = Member->IsDefaultPaged(); if (bIsPagedDefault) { TWeakObjectPtr LiteralPtr(&InLiteral); UpdatePagePickerNames(LiteralPtr); } TSharedPtr DefaultPageArrayHandle = InDetailLayout.AddObjectPropertyData(TArray({ &InLiteral }), UMetasoundEditorGraphMemberDefaultLiteral::GetDefaultsPropertyName()); if (DefaultPageArrayHandle.IsValid()) { uint32 NumElements = 0; TSharedPtr DefaultValueArray = DefaultPageArrayHandle->AsArray(); if (DefaultValueArray.IsValid()) { DefaultValueArray->GetNumElements(NumElements); const bool bHasProjectPageValues = NumElements > 1; // 1 value is only the default constexpr bool bPresetCanEditPageValues = true; const bool bShowPageModifiers = Editor::PageEditorEnabled(Member->GetFrontendBuilderChecked(), bHasProjectPageValues, bPresetCanEditPageValues); if (bIsPagedDefault && bShowPageModifiers) { BuildPageDefaultComboBox(InLiteral, DefaultPageArrayHandle->GetPropertyDisplayName()); } for (uint32 Index = 0; Index < NumElements; ++Index) { TSharedRef ElementProperty = DefaultValueArray->GetElement(Index); TSharedPtr ValueProperty = ElementProperty->GetChildHandle("Value"); if (!ValueProperty.IsValid()) { continue; } DefaultProperties.Add(ValueProperty); IDetailPropertyRow& ValueRow = InDetailLayout.AddPropertyToCategory(ValueProperty); ValueRow.CustomWidget(true /*bShowChilden */); // Name widget if (bIsPagedDefault && bShowPageModifiers) { (*ValueRow.CustomNameWidget()) [ BuildPageDefaultNameWidget(InLiteral, ElementProperty) ]; } else { (*ValueRow.CustomNameWidget()) [ ElementProperty->CreatePropertyNameWidget() ]; } ValueRow.ShowPropertyButtons(false); // Value widget BuildDefaultValueWidget(ValueRow, ValueProperty); ValueRow.IsEnabled(GetEnabled()); } } } } void FMetasoundDefaultLiteralCustomizationBase::BuildDefaultValueWidget(IDetailPropertyRow& ValueRow, TSharedPtr ValueProperty) { if (!ValueProperty.IsValid()) { return; } (*ValueRow.CustomValueWidget()) [ ValueProperty->CreatePropertyValueWidget() ]; } TArray FMetasoundDefaultLiteralCustomizationBase::CustomizeLiteral(UMetasoundEditorGraphMemberDefaultLiteral& InLiteral, IDetailLayoutBuilder& InDetailLayout) { CustomizeDefaults(InLiteral, InDetailLayout); return { }; } TAttribute FMetasoundDefaultLiteralCustomizationBase::GetDefaultVisibility() const { return Visibility; } TAttribute FMetasoundDefaultLiteralCustomizationBase::GetEnabled() const { return Enabled; } void FMetasoundDefaultLiteralCustomizationBase::SetDefaultVisibility(TAttribute InVisibility) { Visibility = InVisibility; } void FMetasoundDefaultLiteralCustomizationBase::SetEnabled(TAttribute InEnabled) { Enabled = InEnabled; } void FMetasoundDefaultLiteralCustomizationBase::SetResetOverride(const TOptional& InResetOverride) { ResetOverride = InResetOverride; } void FMetasoundDefaultLiteralCustomizationBase::UpdatePagePickerNames(TWeakObjectPtr LiteralPtr) { using namespace Frontend; AddablePageStringNames.Reset(); ImplementedPageNames.Reset(); if (!LiteralPtr.IsValid()) { return; } const UMetaSoundSettings* Settings = GetDefault(); check(Settings); TSet ImplementedGuids; LiteralPtr->IterateDefaults([&ImplementedGuids](const FGuid& PageID, FMetasoundFrontendLiteral) { ImplementedGuids.Add(PageID); }); Settings->IteratePageSettings([this, &ImplementedGuids](const FMetaSoundPageSettings& PageSettings) { if (!ImplementedGuids.Contains(PageSettings.UniqueId)) { AddablePageStringNames.Add(MakeShared(PageSettings.Name.ToString())); } }); auto GetPageName = [&Settings](const FGuid& PageID) { const FMetaSoundPageSettings* Page = Settings->FindPageSettings(PageID); if (Page) { return Page->Name; } return Metasound::Editor::GetMissingPageName(PageID); }; Algo::Transform(ImplementedGuids, ImplementedPageNames, GetPageName); } } // namespace Metasound::Editor #undef LOCTEXT_NAMESPACE