// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "NamingTokenData.h" #include "Utils/NamingTokenUtils.h" #include "Widgets/Views/STreeView.h" /** * Represents naming token items in our tree view. */ struct FNamingTokenDataTreeItem : TSharedFromThis { /** The token data, null on namespace entries. */ TSharedPtr NamingTokenData; /** The namespace for this token. May be set regardless of entry type. */ FString Namespace; /** The friendly display name for the namespace. */ FText NamespaceDisplayName; /** Children of a namespace. */ TArray> ChildItems; /** The parent owning this entry. */ TWeakPtr Parent; /** The flattened item index in the tree. */ int32 ItemIndex = INDEX_NONE; /** If this entry is globally registered in the naming tokens subsystem. */ bool bIsGlobal = false; bool ShouldItemBeExpanded() const { return true; } /** Checks if this item matches a filter returning a simple percentage score on the match. */ float MatchesFilter(const FString& FilterText, bool bIsExactNamespaceMatch = false) const { if (FilterText.IsEmpty()) { return 0.5f; } if (IsNamespace()) { if (Namespace == FilterText) // Exact match requires true namespace being typed out... { return 1.f; } if (!bIsExactNamespaceMatch && (Namespace.StartsWith(FilterText, ESearchCase::IgnoreCase) // ...Otherwise, we can check partial match and the display name. || NamespaceDisplayName.ToString().StartsWith(FilterText, ESearchCase::IgnoreCase))) { return 0.75f; } return 0.f; } check(NamingTokenData.IsValid()); if (NamingTokenData->TokenKey.Equals(FilterText, ESearchCase::CaseSensitive)) { return 1.f; } if (NamingTokenData->TokenKey.Equals(FilterText, ESearchCase::IgnoreCase)) { return 0.9f; } if (NamingTokenData->TokenKey.StartsWith(FilterText, ESearchCase::IgnoreCase) || NamingTokenData->DisplayName.ToString().StartsWith(FilterText, ESearchCase::IgnoreCase)) { return 0.75f; } return 0.f; } /** True iff this is considered a namespace entry. */ bool IsNamespace() const { return !NamingTokenData.IsValid(); } /** Convert the item to the best text representation. */ FString ToString(bool bFullyQualified) const { if (NamingTokenData.IsValid()) { return bFullyQualified ? UE::NamingTokens::Utils::CombineNamespaceAndTokenKey(Namespace, NamingTokenData->TokenKey) : NamingTokenData->TokenKey; } return Namespace; } bool operator==(const FNamingTokenDataTreeItem& Other) const { return NamingTokenData == Other.NamingTokenData && Namespace == Other.Namespace; } }; typedef TSharedPtr FNamingTokenDataTreeItemPtr; /** * A tree view for naming token filtering. */ class SNamingTokenDataTreeView : public STreeView { public: void Construct(const FArguments& InArgs, const TSharedPtr& InTreeViewWidget); /** Recursively set expansion state of tree view to match items. */ void SetExpansionStateFromItems(const TArray& InTreeItems); /** Pointer to our owning widget. */ const TWeakPtr& GetOwningTreeViewWidget() const { return OwningTreeViewWidget; } // ~Begin STreeView virtual bool HasKeyboardFocus() const override { return false; } virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) override; // ~End STreeView private: /** The owning tree view widget. */ TWeakPtr OwningTreeViewWidget; }; /** * Each row of the tree view. */ class SNamingTokenDataTreeViewRow final : public SMultiColumnTableRow { public: SLATE_BEGIN_ARGS(SNamingTokenDataTreeViewRow) {} /** The list item for this row. */ SLATE_ARGUMENT(FNamingTokenDataTreeItemPtr, Item) SLATE_END_ARGS() void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTable); // ~Begin SMultiColumnTableRow interface virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override; // ~End SMultiColumnTableRow interface private: static const FEditableTextBoxStyle& GetCustomEditableTextBoxStyle(); private: /** The item associated with this row of data. */ TWeakPtr Item; }; /** * Widget for displaying namespaces and tokens to choose from. */ class SNamingTokenDataTreeViewWidget final : public SCompoundWidget { using FOnSelectionChanged = STreeView::FOnSelectionChanged; DECLARE_DELEGATE_OneParam(FOnItemSelected, FNamingTokenDataTreeItemPtr); public: SLATE_BEGIN_ARGS(SNamingTokenDataTreeViewWidget) {} /** If a selection is changed, such as arrow keys or a single click. */ SLATE_EVENT(FOnSelectionChanged, OnSelectionChanged) /** If an item is formally selected, such as by pressing enter or a double click. */ SLATE_EVENT(FOnItemSelected, OnItemSelected) /** Event if this widget or its children receives focus. */ SLATE_EVENT(FSimpleDelegate, OnFocused) SLATE_END_ARGS() virtual ~SNamingTokenDataTreeViewWidget() override; void Construct(const FArguments& InArgs); /** Populate all possible tree items */ void PopulateTreeItems(); /** Filter down to specific tokens and namespaces. */ void FilterTreeItems(const FString& InFilterText); /** * Provide a key event from the user to determine a navigation path, highlighting an item. * @return True if the key event contained a recognized navigation key. */ bool ForwardKeyEventForNavigation(const FKeyEvent& InKeyEvent); /** Retrieve the currently selected item. */ FNamingTokenDataTreeItemPtr GetSelectedItem() const; /** Retrieve an item given its absolute index. */ FNamingTokenDataTreeItemPtr GetItemFromIndex(int32 InIndex) const; /** Get the token filter text applied. */ const FString& GetTokenFilterText() const { return TokenFilterText; } /** Get the namespace filter text applied. */ const FString& GetNamespaceFilterText() const { return NamespaceFilterText; } /** Informs us we're receiving focus, such as from a child of the treeview. */ void NotifyFocusReceived(); // ~Begin SCompoundWidget virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) override; // ~End SCompoundWidget private: /** Find an item given its index. */ FNamingTokenDataTreeItemPtr GetItemFromIndex(int32 InIndex, const TArray& InItems) const; /** Set the currently selected item given an absolute index. */ void SetItemFromIndex(int32 InIndex); /** Clears the currently selected item. */ void ClearCurrentSelection(); /** Officially complete and report the selection. Called from tab or double-click. */ void FinalizeSelection(FNamingTokenDataTreeItemPtr SelectedItem); public: static FName PrimaryColumnName() { return "Primary"; } private: /** Called by STreeView for each row being generated. */ TSharedRef OnGenerateRowForTree(FNamingTokenDataTreeItemPtr Item, const TSharedRef& OwnerTable); /** Called by STreeView to get child items for the specified parent item. */ void OnGetChildrenForTree(FNamingTokenDataTreeItemPtr InParent, TArray& OutChildren); /** Called when an item in the tree has been collapsed or expanded. */ void OnItemExpansionChanged(FNamingTokenDataTreeItemPtr TreeItem, bool bIsExpanded) const; /** Called when the tree view changes selection. */ void OnTreeViewItemSelectionChanged(FNamingTokenDataTreeItemPtr SelectedItem, ESelectInfo::Type SelectInfo); /** Called when an item in the tree view is double-clicked. */ void OnTreeViewItemDoubleClicked(FNamingTokenDataTreeItemPtr SelectedItem); private: /** Our tree view widget. */ TSharedPtr TreeView; /** The top most items on the tree view without a filter. */ TArray RootTreeItems; /** Items filtered in a search. */ TArray FilteredRootTreeItems; /** The last filter applied. */ FString LastFilterText; /** Last token filter text applied. */ FString TokenFilterText; /** Last namespace filter text applied. */ FString NamespaceFilterText; /** Index of tree item currently selected. */ int32 CurrentSelectionIndex = 0; /** Delegate for when selection changes. */ FOnSelectionChanged ItemSelectionChangedDelegate; /** Delegate for when an item is fully selected. */ FOnItemSelected ItemSelectedDelegate; /** Delegate if we have received focus. */ FSimpleDelegate OnFocusedDelegate; };