// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/BitArray.h" #include "Containers/Map.h" #include "Containers/Set.h" #include "Containers/SparseArray.h" #include "Containers/UnrealString.h" #include "CoreMinimal.h" #include "CoreTypes.h" #include "Delegates/Delegate.h" #include "Framework/SlateDelegates.h" #include "HAL/PlatformCrt.h" #include "Input/Reply.h" #include "Internationalization/Text.h" #include "Misc/Attribute.h" #include "Misc/Optional.h" #include "SlateFwd.h" #include "Templates/SharedPointer.h" #include "Templates/TypeHash.h" #include "Templates/UnrealTemplate.h" #include "Types/SlateConstants.h" #include "Types/SlateEnums.h" #include "UObject/GCObject.h" #include "UObject/NameTypes.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/SWidget.h" #include "Widgets/Views/SExpanderArrow.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STreeView.h" class FReferenceCollector; class ITableRow; class IToolTip; class SEditableTextBox; class SPanel; class SSearchBox; class SWidget; class UEdGraph; class UEdGraphPin; struct FCreateWidgetForActionData; struct FGeometry; struct FGraphActionNode; struct FKeyEvent; struct FPointerEvent; struct FEdGraphSchemaAction; struct FGraphActionListBuilderBase; /** Delegate for hooking up an inline editable text block to be notified that a rename is requested. */ DECLARE_DELEGATE( FOnRenameRequestActionNode ); /** Delegate executed when the mouse button goes down */ DECLARE_DELEGATE_RetVal_OneParam( bool, FCreateWidgetMouseButtonDown, TWeakPtr ); /** Default widget for GraphActionMenu */ class GRAPHEDITOR_API SDefaultGraphActionWidget : public SCompoundWidget { SLATE_BEGIN_ARGS( SDefaultGraphActionWidget ) {} SLATE_ATTRIBUTE(FText, HighlightText) SLATE_END_ARGS() void Construct(const FArguments& InArgs, const FCreateWidgetForActionData* InCreateData); virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; /** The item that we want to display with this widget */ TWeakPtr ActionPtr; /** Delegate executed when mouse button goes down */ FCreateWidgetMouseButtonDown MouseButtonDownDelegate; }; struct FCreateWidgetForActionData { /** True if we want to use the mouse delegate */ bool bHandleMouseButtonDown; /** Delegate for mouse button going down */ FCreateWidgetMouseButtonDown MouseButtonDownDelegate; /** The action being used for the widget */ TSharedPtr< FEdGraphSchemaAction > Action; /** The delegate to determine if the current action is selected in the row */ FIsSelected IsRowSelectedDelegate; /** This will be returned, hooked up to request a rename */ FOnRenameRequestActionNode* const OnRenameRequest; /** The text to highlight */ TAttribute HighlightText; /** True if the widget should be read only - no renaming allowed */ bool bIsReadOnly; FCreateWidgetForActionData(FOnRenameRequestActionNode* const InOnRenameRequest) : OnRenameRequest(InOnRenameRequest) , bIsReadOnly(false) { } }; struct FCustomExpanderData { /** The menu row associated with the widget being customized */ TSharedPtr TableRow; /** The action associated with the menu row being customized */ TSharedPtr RowAction; /** The widget container that the custom expander will belong to */ TSharedPtr WidgetContainer; }; /** Class that displays a list of graph actions and them to be searched and selected */ class GRAPHEDITOR_API SGraphActionMenu : public SCompoundWidget, public FGCObject { public: /** Delegate that can be used to create a widget for a particular action */ DECLARE_DELEGATE_RetVal_OneParam( TSharedRef, FOnCreateWidgetForAction, FCreateWidgetForActionData* const ); /** Delegate that can be used to create a custom "expander" widget for a particular row */ DECLARE_DELEGATE_RetVal_OneParam( TSharedRef, FOnCreateCustomRowExpander, FCustomExpanderData const& ); /** Delegate executed when an action is selected */ DECLARE_DELEGATE_TwoParams( FOnActionSelected, const TArray< TSharedPtr >&, ESelectInfo::Type ); /** Delegate executed when an action is double clicked */ DECLARE_DELEGATE_OneParam( FOnActionDoubleClicked, const TArray< TSharedPtr >& ); /** Delegate executed when an action is dragged */ DECLARE_DELEGATE_RetVal_TwoParams( FReply, FOnActionDragged, const TArray< TSharedPtr >&, const FPointerEvent& ); /** Delegate executed when a category is dragged */ DECLARE_DELEGATE_RetVal_TwoParams( FReply, FOnCategoryDragged, const FText&, const FPointerEvent& ); /** Delegate executed when the list of all actions needs to be refreshed */ DECLARE_DELEGATE_OneParam( FOnCollectAllActions, FGraphActionListBuilderBase& ); /** Delegate executed when the list of all actions needs to be refreshed, should return any sections that should always be visible, even if they don't have children. */ DECLARE_DELEGATE_OneParam( FOnCollectStaticSections, TArray& ) /** Delegate executed when a category is being renamed so any post-rename actions can be handled */ DECLARE_DELEGATE_ThreeParams( FOnCategoryTextCommitted, const FText&, ETextCommit::Type, TWeakPtr< FGraphActionNode >); /** Delegate executed to check if the selected action is valid for renaming */ DECLARE_DELEGATE_RetVal_OneParam( bool, FCanRenameSelectedAction, TWeakPtr< FGraphActionNode > ); /** Delegate to get the name of a section if the widget is a section separator. */ DECLARE_DELEGATE_RetVal_OneParam( FText, FGetSectionTitle, int32 ); /** Delegate to get the tooltip of a section if the widget is a section separator. */ DECLARE_DELEGATE_RetVal_OneParam( TSharedPtr, FGetSectionToolTip, int32 ); /** Delegate to get the widget that appears on the section bar in the section separator. */ DECLARE_DELEGATE_RetVal_TwoParams( TSharedRef, FGetSectionWidget, TSharedRef, int32 ); /** Delegate to get the filter text */ DECLARE_DELEGATE_RetVal( FText, FGetFilterText); /** Delegate to check if an action matches a specified name (used for renaming items etc.) */ DECLARE_DELEGATE_RetVal_TwoParams( bool, FOnActionMatchesName, FEdGraphSchemaAction*, const FName& ); /** Delegate that can be used to create and/or get a custom action list. */ DECLARE_DELEGATE_RetVal(TSharedRef, FOnGetActionList); SLATE_BEGIN_ARGS(SGraphActionMenu) : _AutoExpandActionMenu(false) , _AlphaSortItems(true) , _SortItemsRecursively(true) , _ShowFilterTextBox(true) , _UseSectionStyling(false) , _bAllowPreselectedItemActivation(false) , _DefaultRowExpanderBaseIndentLevel(0) , _GraphObj(nullptr) , _bAutomaticallySelectSingleAction(false) { } SLATE_EVENT( FOnActionSelected, OnActionSelected ) SLATE_EVENT( FOnActionDoubleClicked, OnActionDoubleClicked ) SLATE_EVENT( FOnActionDragged, OnActionDragged ) SLATE_EVENT( FOnCategoryDragged, OnCategoryDragged ) SLATE_EVENT( FOnContextMenuOpening, OnContextMenuOpening ) SLATE_EVENT( FOnCreateWidgetForAction, OnCreateWidgetForAction ) SLATE_EVENT( FOnCreateCustomRowExpander, OnCreateCustomRowExpander ) SLATE_EVENT( FOnGetActionList, OnGetActionList ) SLATE_EVENT( FOnCollectAllActions, OnCollectAllActions ) SLATE_EVENT( FOnCollectStaticSections, OnCollectStaticSections ) SLATE_EVENT( FOnCategoryTextCommitted, OnCategoryTextCommitted ) SLATE_EVENT( FCanRenameSelectedAction, OnCanRenameSelectedAction ) SLATE_EVENT( FGetSectionTitle, OnGetSectionTitle ) SLATE_EVENT( FGetSectionToolTip, OnGetSectionToolTip ) SLATE_EVENT( FGetSectionWidget, OnGetSectionWidget ) SLATE_EVENT( FGetFilterText, OnGetFilterText ) SLATE_EVENT( FOnActionMatchesName, OnActionMatchesName ) SLATE_ARGUMENT( bool, AutoExpandActionMenu ) SLATE_ARGUMENT( bool, AlphaSortItems ) SLATE_ARGUMENT( bool, SortItemsRecursively ) SLATE_ARGUMENT( bool, ShowFilterTextBox ) SLATE_ARGUMENT( bool, UseSectionStyling ) SLATE_ARGUMENT( bool, bAllowPreselectedItemActivation ) SLATE_ARGUMENT( int32, DefaultRowExpanderBaseIndentLevel ) SLATE_ARGUMENT( TArray, DraggedFromPins ) SLATE_ARGUMENT( UEdGraph*, GraphObj ) SLATE_ARGUMENT( bool, bAutomaticallySelectSingleAction ) SLATE_END_ARGS() void Construct( const FArguments& InArgs, bool bIsReadOnly = true ); // FGCObject override virtual void AddReferencedObjects( FReferenceCollector& Collector ) override; virtual FString GetReferencerName() const override; /** * Refreshes the actions that this widget should display * * @param bPreserveExpansion TRUE if the expansion state of the tree should be preserved * @param bHandleOnSelectionEvent TRUE if the item should be selected and any actions that occur with selection will be handled. FALSE and only selection will occur */ void RefreshAllActions(bool bPreserveExpansion, bool bHandleOnSelectionEvent = true); /** Returns a map of all top level sections and their current expansion state. */ void GetSectionExpansion(TMap& SectionExpansion) const; /** Sets the sections to be expanded of all top level sections. */ void SetSectionExpansion(const TMap& SectionExpansion); protected: /** Tree view for showing actions */ TSharedPtr< STreeView< TSharedPtr > > TreeView; /** Text box used for searching for actions */ TSharedPtr FilterTextBox; /** List of all actions we can browser */ TSharedPtr AllActions; /** Flattened list of all actions passing the filter - access via GetFilteredActionNodes to ensure it's up to date */ TArray< TSharedPtr > FilteredActionNodes; /** Root of filtered actions tree */ TSharedPtr FilteredRootAction; /** Stored score for our current selection, so that we can quickly maintain selection when building the list asynchronously */ float SelectedSuggestionScore; /** Stored index in the AllActions list builder that we think is the best fit */ int32 SelectedSuggestionSourceIndex; /** The actual item selected - possibly redundant to selection information in the treeview, but the tree is rebuilt requently */ TSharedPtr SelectedAction; /** Allows us to set selection (via keyboard) without triggering action */ bool bIgnoreUIUpdate; /** Should we auto-expand categories */ bool bAutoExpandActionMenu; /** Should we display the filter text box */ bool bShowFilterTextBox; /** Don't sort items alphabetically */ bool bAlphaSortItems; /** If only the top entries should be sorted or subentries as well */ bool bSortItemsRecursively; /** Should the rows and sections be styled like the details panel? */ bool bUseSectionStyling; /** True if the user is using the keyboard to navigatethe list, halting scoring of newly added entries so that selection is stable */ bool bIsKeyboardNavigating; /** Whether we allow pre-selected items to be activated with a left-click */ bool bAllowPreselectedItemActivation; /** Whether to automatically proceed with an action if it's the only one in the list. */ bool bAutomaticallySelectSingleAction; /** The BaseIndentLevel of the default-created row expander. Not used with OnCreateCustomRowExpander. */ int32 DefaultRowExpanderBaseIndentLevel; /** The slot that the selected item should be displayed at */ int32 DisplayIndex; /** Delegate to call when action is selected */ FOnActionSelected OnActionSelected; /** Delegate to call when action is double clicked */ FOnActionDoubleClicked OnActionDoubleClicked; /** Delegate to call when an action is dragged. */ FOnActionDragged OnActionDragged; /** Delegate to call when a category is dragged. */ FOnCategoryDragged OnCategoryDragged; /** Delegate to call to create widget for an action */ FOnCreateWidgetForAction OnCreateWidgetForAction; /** Delegate to call for creating a custom "expander" widget for indenting a menu row with */ FOnCreateCustomRowExpander OnCreateCustomRowExpander; /** Delegate to call to get a custom action list */ FOnGetActionList OnGetActionList; /** Delegate to call to collect all actions */ FOnCollectAllActions OnCollectAllActions; /** Delegate to call to collect all always visible sections */ FOnCollectStaticSections OnCollectStaticSections; /** Delegate to call to handle any post-category rename events */ FOnCategoryTextCommitted OnCategoryTextCommitted; /** Delegate to call to check if a selected action is valid for renaming */ FCanRenameSelectedAction OnCanRenameSelectedAction; /** Delegate to get the name of a section separator. */ FGetSectionTitle OnGetSectionTitle; /** Delegate to get the tooltip of a section separator. */ FGetSectionToolTip OnGetSectionToolTip; /** Delegate to get the widgets of a section separator. */ FGetSectionWidget OnGetSectionWidget; /** Delegate to get the filter text if supplied from an external source */ FGetFilterText OnGetFilterText; /** Delegate to check if an action matches a specified name (used for renaming items etc.) */ FOnActionMatchesName OnActionMatchesName; public: // SWidget interface virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) override; // End of SWidget interface /** Get filter text box widget */ TSharedRef GetFilterTextBox(); /** Get action that is currently selected */ void GetSelectedActions(TArray< TSharedPtr >& OutSelectedActions) const; /** Initiates a rename on the selected action node, if possible */ void OnRequestRenameOnActionNode(); /** Queries if a rename on the selected action node is possible */ bool CanRequestRenameOnActionNode() const; /** Get category that is currently selected */ FString GetSelectedCategoryName() const; /** Get category child actions that is currently selected */ void GetSelectedCategorySubActions(TArray>& OutActions) const; /** Get category child actions for the passed in action */ void GetCategorySubActions(TWeakPtr InAction, TArray>& OutActions) const; /** * Selects an non-creation item in the list, searching by FName, deselects if name is none * * @param ItemName The name of the item to select * @param SelectInfo The selection type * @param SectionId If known, the section Id to restrict the selection to, useful in the case of categories where they can exist multiple times * @param bIsCategory TRUE if the selection is a category, categories obey different rules and it's hard to re-select properly without this knowledge * @return TRUE if the item was successfully selected or the tree cleared, FALSE if unsuccessful */ bool SelectItemByName(const FName& ItemName, ESelectInfo::Type SelectInfo = ESelectInfo::Direct, int32 SectionId = INDEX_NONE, bool bIsCategory = false ); /** Expands any category with the associated name */ void ExpandCategory(const FText& CategoryName); /* Handler for mouse button going down */ bool OnMouseButtonDownEvent( TWeakPtr InAction ); /** Updates the displayed list starting from IdxStart, useful for async building the display list of actions */ void UpdateForNewActions(int32 IdxStart); /** Regenerated filtered results (FilteredRootAction) based on filter text */ void GenerateFilteredItems(bool bPreserveExpansion); /** The last typed action within the graph action menu */ static FString LastUsedFilterText; protected: /** Get current filter text */ FText GetFilterText() const; /** Change the selection to reflect the active suggestion */ void MarkActiveSuggestion(); /** Try to spawn the node reflected by the active suggestion */ bool TryToSpawnActiveSuggestion(); /** Returns true if the tree should be autoexpanded */ bool ShouldExpandNodes() const; /** Checks if the passed in node is safe for renaming */ bool CanRenameNode(TWeakPtr InNode) const; // Delegates /** Called when filter text changes */ void OnFilterTextChanged( const FText& InFilterText ); /** Called when enter is hit in search box */ void OnFilterTextCommitted(const FText& InText, ETextCommit::Type CommitInfo); /** Get children */ void OnGetChildrenForCategory( TSharedPtr InItem, TArray< TSharedPtr >& OutChildren ); /** Create widget for the supplied node */ TSharedRef MakeWidget( TSharedPtr InItem, const TSharedRef& OwnerTable, bool bIsReadOnly ); /** * Called when tree item is selected * * @param InSelectedItem The action node that is being selected * @param SelectInfo Selection type - Only OnMouseClick and OnKeyPress will trigger a call to HandleSelection */ void OnItemSelected( TSharedPtr< FGraphActionNode > InSelectedItem, ESelectInfo::Type SelectInfo ); /** * Executes the selection delegate providing it has been bound, and the provided action node given is valid and is an action node * * @param InselectedItem The graph action node selected * * @return true if item selection delegate was executed */ bool HandleSelection( TSharedPtr< FGraphActionNode > &InSelectedItem, ESelectInfo::Type InSelectionType ); /** Called when tree item is double clicked */ void OnItemDoubleClicked( TSharedPtr< FGraphActionNode > InClickedItem ); /** Called when tree item dragged */ FReply OnItemDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ); /** Callback when rename text is committed */ void OnNameTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, TWeakPtr< FGraphActionNode > InAction); /** Handler for when an item has scrolled into view after having been requested to do so */ void OnItemScrolledIntoView( TSharedPtr InActionNode, const TSharedPtr& InWidget ); /** Callback for expanding tree items recursively */ void OnSetExpansionRecursive(TSharedPtr InTreeNode, bool bInIsItemExpanded); /** Helper function for adding and scoring actions from our builder */ void ScoreAndAddActions(int32 StartingIndex = INDEX_NONE); /** Helper functions for keyboard interaction */ void SelectPreviousAction(int32 Num = 1); void SelectNextAction(int32 Num = 1); void SelectFirstAction(); void SelectLastAction(); /** General helper functions for accessing the filtered tree */ TSharedPtr GetFirstAction(); const TArray< TSharedPtr >& GetFilteredActionNodes(int32* OutSelectedIndex = nullptr); int32 GetTotalLeafNodes() const; private: /** The pins that have been dragged off of to prompt the creation of this action menu. */ TArray DraggedFromPins; /** The graph that this menu is being constructed in */ UEdGraph* GraphObj; };