// Copyright Epic Games, Inc. All Rights Reserved. #include "SBlueprintLibraryPalette.h" #include "BlueprintActionFilter.h" #include "BlueprintActionMenuBuilder.h" #include "BlueprintActionMenuUtils.h" #include "BlueprintEditor.h" #include "BlueprintPaletteFavorites.h" #include "ClassViewerFilter.h" #include "ClassViewerModule.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "Editor/EditorPerProjectUserSettings.h" #include "Engine/Blueprint.h" #include "Framework/Commands/Commands.h" #include "Framework/Commands/InputChord.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandInfo.h" #include "Framework/Commands/UICommandList.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "Layout/Visibility.h" #include "Modules/ModuleManager.h" #include "SGraphActionMenu.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Types/SlateEnums.h" #include "Types/SlateStructs.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/ObjectPtr.h" #include "UObject/UObjectGlobals.h" #include "UObject/UnrealNames.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SToolTip.h" #include "Widgets/Text/STextBlock.h" class SWidget; struct FSlateBrush; #define LOCTEXT_NAMESPACE "BlueprintLibraryPalette" /******************************************************************************* * Static File Helpers *******************************************************************************/ /** * Contains static helper methods (scoped inside this struct to avoid collisions * during unified builds). */ struct SBlueprintLibraryPaletteUtils { /** The definition of a delegate used to retrieve a set of palette actions */ DECLARE_DELEGATE_OneParam(FPaletteActionGetter, TArray< TSharedPtr >&); /** * Uses the provided ActionGetter to get a list of selected actions, and then * adds every one from the user's favorites. * * @param ActionGetter A delegate to use for grabbing the palette's selected actions. */ static void AddSelectedToFavorites(FPaletteActionGetter ActionGetter) { const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault(); if (ActionGetter.IsBound() && (EditorPerProjectUserSettings->BlueprintFavorites != NULL)) { TArray< TSharedPtr > SelectedActions; ActionGetter.Execute(SelectedActions); EditorPerProjectUserSettings->BlueprintFavorites->AddFavorites(SelectedActions); } } /** * Uses the provided ActionGetter to get a list of selected actions, and then * removes every one from the user's favorites. * * @param ActionGetter A delegate to use for grabbing the palette's selected actions. */ static void RemoveSelectedFavorites(FPaletteActionGetter ActionGetter) { const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault(); if (ActionGetter.IsBound() && (EditorPerProjectUserSettings->BlueprintFavorites != NULL)) { TArray< TSharedPtr > SelectedActions; ActionGetter.Execute(SelectedActions); EditorPerProjectUserSettings->BlueprintFavorites->RemoveFavorites(SelectedActions); } } /** * Utility function used to check if any of the selected actions (returned * by the supplied ActionGetter) are candidates for adding to the user's * favorites. * * @param ActionGetter A delegate that'll retrieve the list of actions that you want tested. * @return True if at least one action (returned by ActionGetter) can be added as a favorite, false if not. */ static bool IsAnyActionFavoritable(FPaletteActionGetter ActionGetter) { bool bCanAnyBeFavorited = false; const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault(); if (ActionGetter.IsBound() && (EditorPerProjectUserSettings->BlueprintFavorites != NULL)) { TArray< TSharedPtr > SelectedActions; ActionGetter.Execute(SelectedActions); for (TSharedPtr Action : SelectedActions) { if (EditorPerProjectUserSettings->BlueprintFavorites->CanBeFavorited(Action) && !EditorPerProjectUserSettings->BlueprintFavorites->IsFavorited(Action)) { bCanAnyBeFavorited = true; break; } } } return bCanAnyBeFavorited; } /** * Utility function used to check if any of the selected actions (returned * by the supplied ActionGetter) are currently one of the user's favorites. * * @param ActionGetter A delegate that'll retrieve the list of actions that you want tested. * @return True if at least one action (returned by ActionGetter) can be removed from the user's favorites, false if not. */ static bool IsAnyActionRemovable(FPaletteActionGetter ActionGetter) { bool bCanAnyBeRemoved = false; const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault(); if (ActionGetter.IsBound() && (EditorPerProjectUserSettings->BlueprintFavorites != NULL)) { TArray< TSharedPtr > SelectedActions; ActionGetter.Execute(SelectedActions); for (TSharedPtr Action : SelectedActions) { if (EditorPerProjectUserSettings->BlueprintFavorites->IsFavorited(Action)) { bCanAnyBeRemoved = true; break; } } } return bCanAnyBeRemoved; } /** String constants shared between multiple SBlueprintLibraryPalette functions */ static FString const LibraryCategoryName; }; FString const SBlueprintLibraryPaletteUtils::LibraryCategoryName = LOCTEXT("PaletteRootCategory", "Library").ToString(); /******************************************************************************* * FBlueprintLibraryPaletteCommands *******************************************************************************/ class FBlueprintLibraryPaletteCommands : public TCommands { public: FBlueprintLibraryPaletteCommands() : TCommands ( "BlueprintLibraryPalette" , LOCTEXT("LibraryPaletteContext", "Library Palette") , NAME_None , FAppStyle::GetAppStyleSetName() ) { } TSharedPtr AddSingleFavorite; TSharedPtr AddSubFavorites; TSharedPtr RemoveSingleFavorite; TSharedPtr RemoveSubFavorites; /** Registers context menu commands for the blueprint library palette. */ virtual void RegisterCommands() override { UI_COMMAND(AddSingleFavorite, "Add to Favorites", "Adds this item to your favorites list.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(AddSubFavorites, "Add Category to Favorites", "Adds all the nodes in this category to your favorites.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(RemoveSingleFavorite, "Remove from Favorites", "Removes this item from your favorites list.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND(RemoveSubFavorites, "Remove Category from Favorites", "Removes all the nodes in this category from your favorites.", EUserInterfaceActionType::Button, FInputChord()); } }; /******************************************************************************* * FPaletteClassFilter *******************************************************************************/ /** Filter to only show classes with blueprint accessible members */ class FPaletteClassFilter : public IClassViewerFilter { public: virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override { const UEdGraphSchema_K2* K2Schema = GetDefault(); return K2Schema->ClassHasBlueprintAccessibleMembers(InClass); } virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override { // @TODO: One day would be nice to see functions on unloaded classes... return false; } }; /******************************************************************************* * SBlueprintLibraryPalette Public Interface *******************************************************************************/ //------------------------------------------------------------------------------ void SBlueprintLibraryPalette::Construct(FArguments const& InArgs, TWeakPtr InBlueprintEditor) { SBlueprintSubPalette::FArguments SuperArgs; SuperArgs._Title = LOCTEXT("PaletteTitle", "Find a Node"); SuperArgs._Icon = FAppStyle::GetBrush("Icons.Search"); SuperArgs._ToolTipText = LOCTEXT("PaletteToolTip", "An all encompassing list of every node that is available for this blueprint."); SuperArgs._ShowFavoriteToggles = true; bUseLegacyLayout = InArgs._UseLegacyLayout.Get(); SBlueprintSubPalette::Construct(SuperArgs, InBlueprintEditor); } /******************************************************************************* * SBlueprintLibraryPalette Private Methods *******************************************************************************/ //------------------------------------------------------------------------------ void SBlueprintLibraryPalette::CollectAllActions(FGraphActionListBuilderBase& OutAllActions) { FString RootCategory = SBlueprintLibraryPaletteUtils::LibraryCategoryName; if (bUseLegacyLayout) { RootCategory = TEXT(""); } FBlueprintActionContext FilterContext; FilterContext.Blueprints.Add(GetBlueprint()); FilterContext.EditorPtr = BlueprintEditorPtr; UClass* ClassFilter = nullptr; if (FilterClass.IsValid()) { ClassFilter = FilterClass.Get(); } FBlueprintActionMenuBuilder PaletteBuilder; FBlueprintActionMenuUtils::MakePaletteMenu(FilterContext, ClassFilter, PaletteBuilder); OutAllActions.Append(PaletteBuilder); } //------------------------------------------------------------------------------ TSharedRef SBlueprintLibraryPalette::ConstructHeadingWidget(FSlateBrush const* const Icon, FText const& TitleText, FText const& InToolTip) { TSharedRef SuperHeading = SBlueprintSubPalette::ConstructHeadingWidget(Icon, TitleText, InToolTip); TSharedPtr ClassPickerToolTip; SAssignNew(ClassPickerToolTip, SToolTip).Text(LOCTEXT("ClassFilter", "Filter the available nodes by class.")); if (bUseLegacyLayout) { SuperHeading = SNew(SVerticalBox).ToolTipText(InToolTip); } SuperHeading->AddSlot() .AutoHeight() .Padding(0.f, 0.f, 0.f, 2.f) [ SNew(SHorizontalBox) .ToolTip(ClassPickerToolTip) // so we still get tooltip text for the empty parts of the SHorizontalBox .Visibility(EVisibility::Visible) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock).Text(LOCTEXT("Class", "Class: ")) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SAssignNew(FilterComboButton, SComboButton) .OnGetMenuContent(this, &SBlueprintLibraryPalette::ConstructClassFilterDropdownContent) .ButtonContent() [ SNew(STextBlock).Text(this, &SBlueprintLibraryPalette::GetFilterClassName) ] ] ]; return SuperHeading; } //------------------------------------------------------------------------------ void SBlueprintLibraryPalette::BindCommands(TSharedPtr CommandListIn) const { SBlueprintSubPalette::BindCommands(CommandListIn); FBlueprintLibraryPaletteCommands::Register(); FBlueprintLibraryPaletteCommands const& PaletteCommands = FBlueprintLibraryPaletteCommands::Get(); struct FActionVisibilityUtils { static bool CanNotRemoveAny(SBlueprintLibraryPaletteUtils::FPaletteActionGetter ActionGetter) { return !SBlueprintLibraryPaletteUtils::IsAnyActionRemovable(ActionGetter); } }; SBlueprintLibraryPaletteUtils::FPaletteActionGetter ActionGetter = SBlueprintLibraryPaletteUtils::FPaletteActionGetter::CreateRaw(GraphActionMenu.Get(), &SGraphActionMenu::GetSelectedActions); CommandListIn->MapAction( PaletteCommands.AddSingleFavorite, FExecuteAction::CreateStatic(&SBlueprintLibraryPaletteUtils::AddSelectedToFavorites, ActionGetter), FCanExecuteAction::CreateStatic(&SBlueprintLibraryPaletteUtils::IsAnyActionFavoritable, ActionGetter), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&FActionVisibilityUtils::CanNotRemoveAny, ActionGetter) ); SBlueprintLibraryPaletteUtils::FPaletteActionGetter CategoryGetter = SBlueprintLibraryPaletteUtils::FPaletteActionGetter::CreateRaw(GraphActionMenu.Get(), &SGraphActionMenu::GetSelectedCategorySubActions); CommandListIn->MapAction( PaletteCommands.AddSubFavorites, FExecuteAction::CreateStatic(&SBlueprintLibraryPaletteUtils::AddSelectedToFavorites, CategoryGetter), FCanExecuteAction::CreateStatic(&SBlueprintLibraryPaletteUtils::IsAnyActionFavoritable, CategoryGetter), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&SBlueprintLibraryPaletteUtils::IsAnyActionFavoritable, CategoryGetter) ); CommandListIn->MapAction( PaletteCommands.RemoveSingleFavorite, FExecuteAction::CreateStatic(&SBlueprintLibraryPaletteUtils::RemoveSelectedFavorites, ActionGetter), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&SBlueprintLibraryPaletteUtils::IsAnyActionRemovable, ActionGetter) ); CommandListIn->MapAction( PaletteCommands.RemoveSubFavorites, FExecuteAction::CreateStatic(&SBlueprintLibraryPaletteUtils::RemoveSelectedFavorites, CategoryGetter), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateStatic(&SBlueprintLibraryPaletteUtils::IsAnyActionRemovable, CategoryGetter) ); } //------------------------------------------------------------------------------ void SBlueprintLibraryPalette::GenerateContextMenuEntries(FMenuBuilder& MenuBuilder) const { if (!bUseLegacyLayout) { FBlueprintLibraryPaletteCommands const& PaletteCommands = FBlueprintLibraryPaletteCommands::Get(); MenuBuilder.BeginSection("Favorites"); { TSharedPtr SelectedAction = GetSelectedAction(); // if we have a specific action selected if (SelectedAction.IsValid()) { MenuBuilder.AddMenuEntry(PaletteCommands.AddSingleFavorite); MenuBuilder.AddMenuEntry(PaletteCommands.RemoveSingleFavorite); } // if we have a category selected { FString CategoryName = GraphActionMenu->GetSelectedCategoryName(); // make sure it is an actual category and isn't the root (assume there's only one category with that name) if (!CategoryName.IsEmpty() && (CategoryName != SBlueprintLibraryPaletteUtils::LibraryCategoryName)) { MenuBuilder.AddMenuEntry(PaletteCommands.AddSubFavorites); MenuBuilder.AddMenuEntry(PaletteCommands.RemoveSubFavorites); } } } MenuBuilder.EndSection(); MenuBuilder.BeginSection("ListActions"); SBlueprintSubPalette::GenerateContextMenuEntries(MenuBuilder); MenuBuilder.EndSection(); } } //------------------------------------------------------------------------------ TSharedRef SBlueprintLibraryPalette::ConstructClassFilterDropdownContent() { FClassViewerInitializationOptions Options; Options.Mode = EClassViewerMode::ClassPicker; Options.DisplayMode = EClassViewerDisplayMode::TreeView; Options.ClassFilters.Add(MakeShareable(new FPaletteClassFilter)); // create a class picker for the drop-down TSharedRef ClassPickerWidget = FModuleManager::LoadModuleChecked("ClassViewer").CreateClassViewer(Options, FOnClassPicked::CreateSP(this, &SBlueprintLibraryPalette::OnClassPicked)); TSharedPtr ClearFilterToolTip; SAssignNew(ClearFilterToolTip, SToolTip).Text(LOCTEXT("ClearFilter", "Clears the class filter so you can see all available nodes for placement.")); return SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) [ // achieving fixed width by nesting items within a fixed width box. SNew(SBox) .WidthOverride(350.0f) [ SNew(SVerticalBox) // 'All' button +SVerticalBox::Slot() .Padding(2.f, 0.f, 2.f, 2.f) [ SNew(SButton) .OnClicked(this, &SBlueprintLibraryPalette::ClearClassFilter) .ToolTip(ClearFilterToolTip) [ SNew(STextBlock).Text(LOCTEXT("All", "All")) ] ] // Class picker +SVerticalBox::Slot() .MaxHeight(400.0f) .AutoHeight() [ ClassPickerWidget ] ] ]; } //------------------------------------------------------------------------------ FText SBlueprintLibraryPalette::GetFilterClassName() const { FText FilterDisplayString = LOCTEXT("All", "All"); if (FilterClass != NULL) { UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(FilterClass.Get()); FilterDisplayString = FText::FromString((Blueprint != NULL) ? Blueprint->GetName() : FilterClass->GetName()); } return FilterDisplayString; } //------------------------------------------------------------------------------ FReply SBlueprintLibraryPalette::ClearClassFilter() { FilterComboButton->SetIsOpen(false); if (FilterClass.IsValid()) { FilterClass = NULL; RefreshActionsList(true); } return FReply::Handled(); } //------------------------------------------------------------------------------ void SBlueprintLibraryPalette::OnClassPicked(UClass* PickedClass) { FilterClass = PickedClass; FilterComboButton->SetIsOpen(false); RefreshActionsList(true); } #undef LOCTEXT_NAMESPACE