// Copyright Epic Games, Inc. All Rights Reserved. #include "TargetPlatformAudioCustomization.h" #include "EditorDirectories.h" #include "DetailWidgetRow.h" #include "IDetailPropertyRow.h" #include "IDetailChildrenBuilder.h" #include "UObject/UnrealType.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Views/SListView.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "Widgets/Input/SEditableTextBox.h" #include "Features/IModularFeatures.h" #include "PropertyCustomizationHelpers.h" #include "Widgets/SToolTip.h" #if WITH_ENGINE #include "AudioDevice.h" #endif IMPLEMENT_MODULE(FDefaultModuleImpl, AudioSettingsEditor) #define LOCTEXT_NAMESPACE "PlatformAudio" // This string is used for the item on the combo box that, when selected, defers to the custom string entry. static const TCHAR* ManualEntryItem = TEXT("Other"); FAudioPluginWidgetManager::FAudioPluginWidgetManager() { ManualReverbEntry = TSharedPtr(new FText(FText::FromString(TEXT("Built-in Reverb")))); ManualSpatializationEntry = TSharedPtr(new FText(FText::FromString(TEXT("Built-in Spatialization")))); ManualOcclusionEntry = TSharedPtr(new FText(FText::FromString(TEXT("Built-in Occlusion")))); } FAudioPluginWidgetManager::~FAudioPluginWidgetManager() { } TSharedRef FAudioPluginWidgetManager::MakeAudioPluginSelectorWidget(const TSharedPtr& PropertyHandle, EAudioPlugin AudioPluginType, const FString& PlatformName) { TArray>* ValidPluginNames = nullptr; FText TooltipText; TSharedPtr DefaultEffectName; switch (AudioPluginType) { case EAudioPlugin::SPATIALIZATION: ValidPluginNames = &SpatializationPlugins; TooltipText = LOCTEXT("Spatialization", "Choose which audio plugin should be used for spatialization. If your desired spatialization isn't found in the drop down menu, ensure that it is enabled on the Plugins panel."); DefaultEffectName = TSharedPtr(new FText(FText::FromString(TEXT("Built-in Spatialization")))); SelectedSpatialization = TSharedPtr(new FText(*DefaultEffectName)); PropertyHandle->GetValueAsDisplayText(*SelectedSpatialization); break; case EAudioPlugin::REVERB: ValidPluginNames = &ReverbPlugins; TooltipText = LOCTEXT("Reverb", "Choose which audio plugin should be used for reverb. If your desired reverb plugin isn't found in the drop down menu, ensure that it is enabled on the Plugins panel."); DefaultEffectName = TSharedPtr(new FText(FText::FromString(TEXT("Built-in Reverb")))); SelectedReverb = TSharedPtr(new FText(*DefaultEffectName)); PropertyHandle->GetValueAsDisplayText(*SelectedReverb); break; case EAudioPlugin::OCCLUSION: ValidPluginNames = &OcclusionPlugins; TooltipText = LOCTEXT("Occlusion", "Choose which audio plugin should be used for occlusion. If your desired occlusion plugin isn't found in the drop down menu, ensure that it is enabled on the Plugins panel."); DefaultEffectName = TSharedPtr(new FText(FText::FromString(TEXT("Built-in Occlusion")))); SelectedOcclusion = TSharedPtr(new FText(*DefaultEffectName)); PropertyHandle->GetValueAsDisplayText(*SelectedOcclusion); break; default: checkf(false, TEXT("Invalid plugin enumeration type. Need to add a handle for that case here.")); break; } ValidPluginNames->Add(DefaultEffectName); #if WITH_ENGINE // Scan through all currently enabled audio plugins of this specific type: switch (AudioPluginType) { case EAudioPlugin::SPATIALIZATION: { TArray AvailableSpatializationPlugins = IModularFeatures::Get().GetModularFeatureImplementations(IAudioSpatializationFactory::GetModularFeatureName()); for (IAudioSpatializationFactory* Plugin : AvailableSpatializationPlugins) { if (Plugin->SupportsPlatform(PlatformName)) { ValidPluginNames->Add(TSharedPtr(new FText(FText::FromString(Plugin->GetDisplayName())))); } } } break; case EAudioPlugin::REVERB: { TArray AvailableReverbPlugins = IModularFeatures::Get().GetModularFeatureImplementations(IAudioReverbFactory::GetModularFeatureName()); for (IAudioReverbFactory* Plugin : AvailableReverbPlugins) { if (Plugin->SupportsPlatform(PlatformName)) { ValidPluginNames->Add(TSharedPtr(new FText(FText::FromString(Plugin->GetDisplayName())))); } } } break; case EAudioPlugin::OCCLUSION: { TArray AvailableOcclusionPlugins = IModularFeatures::Get().GetModularFeatureImplementations(IAudioOcclusionFactory::GetModularFeatureName()); for (IAudioOcclusionFactory* Plugin : AvailableOcclusionPlugins) { if (Plugin->SupportsPlatform(PlatformName)) { ValidPluginNames->Add(TSharedPtr(new FText(FText::FromString(Plugin->GetDisplayName())))); } } } break; default: break; } #endif // #if WITH_ENGINE // This pointer is used to store whatever custom string was input by the user or retrieved from the config file. ValidPluginNames->Add(TSharedPtr(new FText(FText::FromString(ManualEntryItem)))); // Text box component: TSharedRef EditableTextBox = SNew(SEditableTextBox) .Text_Lambda([this, AudioPluginType]() { return OnGetPluginText(AudioPluginType); }) .OnTextCommitted_Raw(this, &FAudioPluginWidgetManager::OnPluginTextCommitted, AudioPluginType, PropertyHandle) .SelectAllTextWhenFocused(true) .RevertTextOnEscape(true); // Combo box component: TSharedRef ComboBox = SNew(SListView>) .ListItemsSource(ValidPluginNames) .ScrollbarVisibility(EVisibility::Collapsed) .OnGenerateRow_Lambda([](TSharedPtr InItem, const TSharedRef< class STableViewBase >& Owner) { return SNew(STableRow>, Owner) .Padding(FMargin(16, 4, 16, 4)) [ SNew(STextBlock).Text(*InItem) ]; }) .OnSelectionChanged_Lambda([this, AudioPluginType, PropertyHandle](TSharedPtr InText, ESelectInfo::Type) { const bool bSelectedManualEntry = (InText->ToString() == FString(ManualEntryItem)); switch (AudioPluginType) { case EAudioPlugin::SPATIALIZATION: if (bSelectedManualEntry) { SelectedSpatialization = ManualSpatializationEntry; } else { SelectedSpatialization = InText; } OnPluginSelected(bSelectedManualEntry ? ManualSpatializationEntry->ToString() : InText->ToString(), PropertyHandle); break; case EAudioPlugin::REVERB: if (bSelectedManualEntry) { SelectedReverb = ManualReverbEntry; } else { SelectedReverb = InText; } OnPluginSelected(bSelectedManualEntry ? ManualReverbEntry->ToString() : InText->ToString(), PropertyHandle); break; case EAudioPlugin::OCCLUSION: if (bSelectedManualEntry) { SelectedOcclusion = ManualOcclusionEntry; } else { SelectedOcclusion = InText; } OnPluginSelected((bSelectedManualEntry ? ManualOcclusionEntry->ToString() : InText->ToString()), PropertyHandle); break; default: break; } }); //Generate widget: const TSharedRef NewWidget = SNew(SComboButton) .ContentPadding(FMargin(0, 0, 5, 0)) .ToolTipText(TooltipText) .ButtonContent() [ SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("NoBorder")) .Padding(FMargin(0, 0, 5, 0)) [ EditableTextBox ] ] .MenuContent() [ ComboBox ]; return NewWidget; } void FAudioPluginWidgetManager::BuildAudioCategory(IDetailLayoutBuilder& DetailLayout, const FString& PlatformName, const UStruct* ClassOuterMost) { IDetailCategoryBuilder& AudioCategory = DetailLayout.EditCategory(TEXT("Audio")); TSharedPtr AudioSpatializationPropertyHandle = DetailLayout.GetProperty("SpatializationPlugin", ClassOuterMost); IDetailPropertyRow& AudioSpatializationPropertyRow = AudioCategory.AddProperty(AudioSpatializationPropertyHandle); AudioSpatializationPropertyRow.CustomWidget() .NameContent() [ AudioSpatializationPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MaxDesiredWidth(500.0f) .MinDesiredWidth(100.0f) [ MakeAudioPluginSelectorWidget(AudioSpatializationPropertyHandle, EAudioPlugin::SPATIALIZATION, PlatformName) ]; TSharedPtr AudioReverbPropertyHandle = DetailLayout.GetProperty("ReverbPlugin", ClassOuterMost); IDetailPropertyRow& AudioReverbPropertyRow = AudioCategory.AddProperty(AudioReverbPropertyHandle); AudioReverbPropertyRow.CustomWidget() .NameContent() [ AudioReverbPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MaxDesiredWidth(500.0f) .MinDesiredWidth(100.0f) [ MakeAudioPluginSelectorWidget(AudioReverbPropertyHandle, EAudioPlugin::REVERB, PlatformName) ]; TSharedPtr AudioOcclusionPropertyHandle = DetailLayout.GetProperty("OcclusionPlugin", ClassOuterMost); IDetailPropertyRow& AudioOcclusionPropertyRow = AudioCategory.AddProperty(AudioOcclusionPropertyHandle); AudioOcclusionPropertyRow.CustomWidget() .NameContent() [ AudioOcclusionPropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MaxDesiredWidth(500.0f) .MinDesiredWidth(100.0f) [ MakeAudioPluginSelectorWidget(AudioOcclusionPropertyHandle, EAudioPlugin::OCCLUSION, PlatformName) ]; // Not really a plugin, but this is common to all TargetPlatforms TSharedPtr SoundQualityNamePropHandle = DetailLayout.GetProperty("SoundCueCookQualityIndex", ClassOuterMost); ensure(SoundQualityNamePropHandle.IsValid()); IDetailPropertyRow& SoundQualityNamePropRow = AudioCategory.AddProperty(SoundQualityNamePropHandle); SoundQualityNamePropRow.CustomWidget() .NameContent() [ SoundQualityNamePropHandle->CreatePropertyNameWidget() ] .ValueContent() .MaxDesiredWidth(500.0f) .MinDesiredWidth(100.0f) [ PropertyCustomizationHelpers::MakePropertyComboBox( SoundQualityNamePropHandle, FOnGetPropertyComboBoxStrings::CreateLambda([](TArray>& OutComboBoxStrings, TArray>& OutToolTips, TArray& OutRestrictedItems) -> void { for (const FAudioQualitySettings& i : GetDefault()->QualityLevels) { OutComboBoxStrings.Add(MakeShared(i.DisplayName.ToString())); OutRestrictedItems.Add(false); OutToolTips.Add(SNew(SToolTip).Text(i.DisplayName)); } }), FOnGetPropertyComboBoxValue::CreateLambda([SoundQualityNamePropHandle]() -> FString { int32 IndexVal = INDEX_NONE; SoundQualityNamePropHandle->GetValue(IndexVal); return GetDefault()->FindQualityNameByIndex(IndexVal); }), FOnPropertyComboBoxValueSelected::CreateLambda([SoundQualityNamePropHandle](FString Value) -> void { const TArray& Values = GetDefault()->QualityLevels; int32 Index = Values.IndexOfByPredicate([TextValue = FText::FromString(Value)](const FAudioQualitySettings& i) -> bool { return i.DisplayName.CompareTo(TextValue) == 0; }); SoundQualityNamePropHandle->SetValue(Index); }) ) ]; } void FAudioPluginWidgetManager::OnPluginSelected(FString PluginName, TSharedPtr PropertyHandle) { PropertyHandle->SetValue(PluginName); } void FAudioPluginWidgetManager::OnPluginTextCommitted(const FText& InText, ETextCommit::Type CommitType, EAudioPlugin AudioPluginType, TSharedPtr PropertyHandle) { switch (AudioPluginType) { case EAudioPlugin::SPATIALIZATION: *ManualSpatializationEntry = InText; SelectedSpatialization = ManualSpatializationEntry; break; case EAudioPlugin::REVERB: *ManualReverbEntry = InText; SelectedReverb = ManualReverbEntry; break; case EAudioPlugin::OCCLUSION: *ManualOcclusionEntry = InText; SelectedOcclusion = ManualOcclusionEntry; break; default: break; } OnPluginSelected(InText.ToString(), PropertyHandle); } FText FAudioPluginWidgetManager::OnGetPluginText(EAudioPlugin AudioPluginType) { switch (AudioPluginType) { case EAudioPlugin::SPATIALIZATION: return *SelectedSpatialization; break; case EAudioPlugin::REVERB: return *SelectedReverb; break; case EAudioPlugin::OCCLUSION: return *SelectedOcclusion; break; default: return FText::FromString(FString(TEXT("ERROR"))); break; } } #undef LOCTEXT_NAMESPACE