// Copyright 2024 - Michal Smoleň #include "NiagaraUIContentBrowserExtension.h" #include "ContentBrowserDelegates.h" #include "ContentBrowserModule.h" #include "NiagaraUIRendererEditorStyle.h" #include "Kismet2/BlueprintEditorUtils.h" #include "MaterialEditingLibrary.h" #include "MaterialGraph/MaterialGraph.h" #include "Materials/MaterialExpressionVertexColor.h" #include "Materials/MaterialExpressionParticleColor.h" #include "IContentBrowserSingleton.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #if ENGINE_MINOR_VERSION >= 2 #include "MaterialDomain.h" #endif #define LOCTEXT_NAMESPACE "NiagaraUIRenderer" static FContentBrowserMenuExtender_SelectedAssets ContentBrowserExtenderDelegate; static FDelegateHandle ContentBrowserExtenderDelegateHandle; struct FContentBrowserSelectedAssetExtensionBase { public: TArray SelectedAssets; public: virtual void Execute() {} virtual ~FContentBrowserSelectedAssetExtensionBase() {} }; #include "IAssetTools.h" #include "AssetToolsModule.h" struct FCreateNiagaraUIMaterialsExtension : public FContentBrowserSelectedAssetExtensionBase { void CreateNiagaraUIMaterials(TArray& Materials) { FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked("ContentBrowser"); const FString DefaultSuffix = TEXT("_UI"); TArray NewMaterials; for (const auto Material : Materials) { FString Name; FString PackageName; AssetToolsModule.Get().CreateUniqueAssetName(Material->GetOutermost()->GetName(), DefaultSuffix, PackageName, Name); FString TargetPackagePath = FPackageName::GetLongPackagePath(PackageName); UObject* DuplicatedObject = AssetToolsModule.Get().DuplicateAsset(Name, TargetPackagePath, Material); UMaterial* NewMaterial = Cast(DuplicatedObject); if (!NewMaterial) return; NewMaterials.Add(NewMaterial); NewMaterial->MaterialDomain = EMaterialDomain::MD_UI; #if ENGINE_MINOR_VERSION < 1 for (UMaterialExpression* Expression : NewMaterial->Expressions) #else for (UMaterialExpression* Expression : NewMaterial->GetExpressions()) #endif { //if (UMaterialExpressionParticleColor* ParticleColor = Cast(Expression)) //if (Expression->IsA(UMaterialExpressionParticleColor::StaticClass())) if (Expression->GetKeywords().ToString() == TEXT("particle color")) { UMaterialExpressionVertexColor* VertexColor = Cast( UMaterialEditingLibrary::CreateMaterialExpression(NewMaterial, UMaterialExpressionVertexColor::StaticClass(), Expression->MaterialExpressionEditorX, Expression->MaterialExpressionEditorY)); for (int32 InputIndex = 0; InputIndex < MP_MAX; InputIndex++) { FExpressionInput* Input = NewMaterial->GetExpressionInputForProperty((EMaterialProperty)InputIndex); if (Input && Input->Expression == Expression) { Input->Expression = VertexColor; } } FExpressionInput* EmissiveInput = NewMaterial->GetExpressionInputForProperty(EMaterialProperty::MP_EmissiveColor); FExpressionInput* BaseInput = NewMaterial->GetExpressionInputForProperty(EMaterialProperty::MP_BaseColor); if (EmissiveInput->Expression == nullptr && BaseInput->Expression) EmissiveInput->Expression = BaseInput->Expression; #if ENGINE_MINOR_VERSION < 1 for (UMaterialExpression* TestExp : NewMaterial->Expressions) #else for (UMaterialExpression* TestExp : NewMaterial->GetExpressions()) #endif { #if ENGINE_MINOR_VERSION <= 2 TArray Inputs = TestExp->GetInputs(); for (FExpressionInput* Input : Inputs) #elif ENGINE_MINOR_VERSION < 5 TArrayView Inputs = TestExp->GetInputsView(); for (FExpressionInput* Input : Inputs) #else for (FExpressionInputIterator Input(TestExp); Input; ++Input) #endif { if (Input->Expression == Expression) { Input->Expression = VertexColor; } } } UMaterialEditingLibrary::DeleteMaterialExpression(NewMaterial, Expression); } } NewMaterial->PreEditChange(nullptr); NewMaterial->PostEditChange(); NewMaterial->Modify(); } if (NewMaterials.Num() == 1) { ContentBrowserModule.Get().SyncBrowserToAssets(NewMaterials); // TODO: Automatically rename } else if (NewMaterials.Num() > 1) { ContentBrowserModule.Get().SyncBrowserToAssets(NewMaterials); } } virtual void Execute() override { TArray Materials; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& AssetData = *AssetIt; if (UMaterial* Material = Cast(AssetData.GetAsset())) { Materials.Add(Material); } } CreateNiagaraUIMaterials(Materials); } }; class FNiagaraUIContentBrowserExtension_Impl { public: static void ExecuteSelectedContentFunctor(TSharedPtr SelectedAssetFunctor) { SelectedAssetFunctor->Execute(); } static void CreateNiagaraUIFunctions(FMenuBuilder& MenuBuilder, TArray SelectedAssets) { TArray Materials; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& AssetData = *AssetIt; if (UMaterial* Material = Cast(AssetData.GetAsset())) { Materials.Add(Material); } } TSharedPtr NiagaraUIMaterialsFunctor = MakeShareable(new FCreateNiagaraUIMaterialsExtension()); NiagaraUIMaterialsFunctor->SelectedAssets = SelectedAssets; FUIAction ActionCreateNiagaraUIMaterial(FExecuteAction::CreateStatic(&FNiagaraUIContentBrowserExtension_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr(NiagaraUIMaterialsFunctor))); MenuBuilder.AddMenuEntry( LOCTEXT("NiagaraUIRenderer_CreateUIMaterial", "Create Niagara UI Material"), LOCTEXT("NiagaraUIRenderer_CreateUIMaterialTooltip", "Creates a new version of this material that is suitable to be used for the Niagara UI Particles. Be aware that manual inspection of this material is recommended."), FSlateIcon(FNiagaraUIRendererEditorStyle::GetStyleSetName(), "NiagaraUIRendererEditorStyle.ParticleIcon"), ActionCreateNiagaraUIMaterial, NAME_None, EUserInterfaceActionType::Button ); } static TSharedRef OnExtendContentBrowserAssetSelectionMenu(const TArray& SelectedAssets) { TSharedRef Extender(new FExtender()); bool AnyMaterials = false; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& Asset = *AssetIt; #if ENGINE_MINOR_VERSION < 1 AnyMaterials = AnyMaterials || (Asset.AssetClass == UMaterial::StaticClass()->GetFName()); #else AnyMaterials = AnyMaterials || (Asset.AssetClassPath == UMaterial::StaticClass()->GetClassPathName()); #endif } if (AnyMaterials) { Extender->AddMenuExtension( "GetAssetActions", EExtensionHook::After, nullptr, FMenuExtensionDelegate::CreateStatic(&FNiagaraUIContentBrowserExtension_Impl::CreateNiagaraUIFunctions, SelectedAssets)); } return Extender; } static TArray& GetExtenderDelegates() { FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked(TEXT("ContentBrowser")); return ContentBrowserModule.GetAllAssetViewContextMenuExtenders(); } }; void FNiagaraUIContentBrowserExtension::InstallHooks() { ContentBrowserExtenderDelegate = FContentBrowserMenuExtender_SelectedAssets::CreateStatic(&FNiagaraUIContentBrowserExtension_Impl::OnExtendContentBrowserAssetSelectionMenu); TArray& CBMenuExtenderDelegates = FNiagaraUIContentBrowserExtension_Impl::GetExtenderDelegates(); CBMenuExtenderDelegates.Add(ContentBrowserExtenderDelegate); ContentBrowserExtenderDelegateHandle = CBMenuExtenderDelegates.Last().GetHandle(); } void FNiagaraUIContentBrowserExtension::RemoveHooks() { TArray& CBMenuExtenderDelegates = FNiagaraUIContentBrowserExtension_Impl::GetExtenderDelegates(); CBMenuExtenderDelegates.RemoveAll([](const FContentBrowserMenuExtender_SelectedAssets& Delegate){ return Delegate.GetHandle() == ContentBrowserExtenderDelegateHandle; }); } #undef LOCTEXT_NAMESPACE