Added a bunch more stuff. Too much to care to mention. Still more to do though.

This commit is contained in:
Jamie Greunbaum
2023-09-30 01:25:51 -04:00
parent cfe285b94d
commit 102927a690
60 changed files with 1664 additions and 76 deletions
@@ -0,0 +1,28 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
struct FComboActionSearchFilter
{
public:
bool IsEmptyFilter() const
{
return SearchString.IsEmpty()
&& bIncludeNodeTitle == false
&& bIncludeNodeType == false
&& bIncludeNodeDecoratorsTypes == false
&& bIncludeNodeData == true
&& bIncludeNodeGUID == false;
}
public:
// Search term that the search items must match
FString SearchString;
bool bIncludeNodeTitle = true;
bool bIncludeNodeType = true;
bool bIncludeNodeDecoratorsTypes = true;
bool bIncludeNodeData = true;
bool bIncludeNodeGUID = false;
};
@@ -0,0 +1,275 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionSearchManager.h"
#include "ComboActionGraph.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Ed/EdComboActionGraph.h"
#include "Nodes/ComboActionGraphNode.h"
#include "Nodes/ComboActionGraphNode_ActionNodeBase.h"
#include "Search/ComboActionSearchFilter.h"
#define LOCTEXT_NAMESPACE "ComboActionSearchManager"
FComboActionSearchManager *FComboActionSearchManager::Instance = nullptr;
bool FComboActionSearchManager::QueryGraphNode(const FComboActionSearchFilter &SearchFilter, const UEdComboActionGraphNode *InGraphNode, const TSharedPtr<FComboActionSearchResult> &OutParentNode) const
{
if (SearchFilter.SearchString.IsEmpty() || !OutParentNode.IsValid() || !IsValid(InGraphNode))
{
return false;
}
bool bContainsSearchString = false;
const UComboActionGraphNode *Node = InGraphNode->ComboActionGraphNode;
if (Node == nullptr)
{
return false;
}
const FString NodeType = Node->NodeTypeName.ToString();
const FText DisplayText = FText::Format(LOCTEXT("ComboActionNodeCategory", "Found results in {0}"), FText::FromString(NodeType));
const TSharedPtr<FComboActionSearchResult_GraphNode> TreeGraphNode = MakeShared<FComboActionSearchResult_GraphNode>(DisplayText, OutParentNode);
TreeGraphNode->SetCategory(FText::FromString(NodeType));
TreeGraphNode->SetGraphNode(InGraphNode);
if (bContainsSearchString)
{
OutParentNode->AddChild(TreeGraphNode);
}
// Search by Title
if (SearchFilter.bIncludeNodeTitle)
{
if (Node->NodeTitle.ToString().Contains(SearchFilter.SearchString))
{
bContainsSearchString = true;
MakeChildTextNode
(
TreeGraphNode,
FText::FromName(FName(Node->NodeTitle.ToString() )),
LOCTEXT("NodeTitleKey", "Node Title"),
TEXT("Node Title")
);
}
}
// Search by NodeTypeName
if (SearchFilter.bIncludeNodeType)
{
if (Node->NodeTypeName.ToString().Contains(SearchFilter.SearchString))
{
bContainsSearchString = true;
this->MakeChildTextNode(
TreeGraphNode,
FText::FromName(FName(Node->NodeTypeName.ToString() )),
LOCTEXT("NodeTypeKey", "Node Type"),
TEXT("Node Type")
);
}
}
// Search by Decorators
if (SearchFilter.bIncludeNodeDecoratorsTypes)
{
const TArray<FComboActionDecorator> &NodeDecorators = Node->GetNodeDecorators();
for (int32 Index = 0, Num = NodeDecorators.Num(); Index < Num; Index++)
{
bContainsSearchString = this->QueryNodeDecorators(
SearchFilter,
NodeDecorators[Index],
TreeGraphNode,
Index,
TEXT("DecoratorType")
)
|| bContainsSearchString;
}
}
// Search by Node Data
if (SearchFilter.bIncludeNodeData)
{
if (const UComboActionGraphNode_ActionNodeBase *ActionNodeBase = Cast<UComboActionGraphNode_ActionNodeBase>(Node))
{
if (ActionNodeBase->GetRowName().ToString().Contains(SearchFilter.SearchString))
{
bContainsSearchString = true;
this->MakeChildTextNode(
TreeGraphNode,
FText::FromName(FName(Node->NodeTypeName.ToString() )),
LOCTEXT("NodeDataRowKey", "Node Data"),
TEXT("Node Data")
);
}
}
}
// Search by GUID
if (SearchFilter.bIncludeNodeGUID)
{
const FString FoundGUID = Node->GetNodeGUID().ToString();
if (FoundGUID.Contains(SearchFilter.SearchString))
{
bContainsSearchString = true;
MakeChildTextNode
(
TreeGraphNode,
FText::FromString(FoundGUID),
LOCTEXT("NodeGUID", "Node GUID"),
TEXT("Node GUID")
);
}
}
if (bContainsSearchString)
{
OutParentNode->AddChild(TreeGraphNode);
}
return bContainsSearchString;
}
bool FComboActionSearchManager::QueryNodeDecorators(const FComboActionSearchFilter &SearchFilter, const FComboActionDecorator &InDecorator, const TSharedPtr<FComboActionSearchResult> &OutParentNode, int32 DecoratorIndex, FName DecoratorMemberName) const
{
if (SearchFilter.SearchString.IsEmpty() || !OutParentNode.IsValid())
{
return false;
}
bool bContainsSearchString = false;
if (InDecorator.DecoratorType == nullptr) return false;
// Search by Decorator Name
if (InDecorator.DecoratorType->GetName().Contains(SearchFilter.SearchString))
{
bContainsSearchString = true;
FString DecoratorName = InDecorator.DecoratorType->GetClass()->GetName();
// Format Name
{
if (DecoratorName.Contains(TEXT("_GEN_VARIABLE")))
{
DecoratorName.ReplaceInline(TEXT("_GEN_VARIABLE"), TEXT(""));
}
if(DecoratorName.EndsWith(TEXT("_C")) && DecoratorName.StartsWith(TEXT("Default__")))
{
DecoratorName.RightChopInline(9);
DecoratorName.LeftChopInline(2);
}
if (DecoratorName.EndsWith(TEXT("_C")))
{
DecoratorName.LeftChopInline(2);
}
}
const FText Category = FText::Format
(
LOCTEXT("DecoratorName", "Node Decorator: {0} at Index: {1}"),
FText::FromString(DecoratorName), FText::AsNumber(DecoratorIndex)
);
MakeChildTextNode
(
OutParentNode,
FText::FromString(DecoratorName),
Category,
Category.ToString()
);
}
return bContainsSearchString;
}
bool FComboActionSearchManager::QuerySingleAction(const FComboActionSearchFilter &SearchFilter, const UComboActionGraph *InAction, TSharedPtr<FComboActionSearchResult> &OutParentNode)
{
if (SearchFilter.SearchString.IsEmpty() || !OutParentNode.IsValid() || !IsValid(InAction))
{
return false;
}
const UEdComboActionGraph *Graph = CastChecked<UEdComboActionGraph>(InAction->EdGraph);
const TSharedPtr<FComboActionSearchResult_ActionNode> TreeActionNode = MakeShared<FComboActionSearchResult_ActionNode>(FText::FromString(InAction->GetPathName()), OutParentNode);
TreeActionNode->SetActionGraph(Graph);
// Find in GraphNodes
bool bFoundInAction = false;
const TArray<UEdGraphNode*> &AllGraphNodes = Graph->Nodes;
for (UEdGraphNode *Node : AllGraphNodes)
{
bool bFoundInNode = false;
if (const UEdComboActionGraphNode *GraphNode = Cast<UEdComboActionGraphNode>(Node))
{
bFoundInNode = this->QueryGraphNode(SearchFilter, GraphNode, TreeActionNode);
}
// Found at least one match in one of the nodes.
bFoundInAction = bFoundInNode || bFoundInAction;
}
if (bFoundInAction)
{
OutParentNode->AddChild(TreeActionNode);
}
return bFoundInAction;
}
void FComboActionSearchManager::Initialize(TSharedPtr<FWorkspaceItem> ParentTabCategory)
{
// Must ensure we do not attempt to load the AssetRegistry Module while saving a package, however, if it is loaded already we can safely obtain it
this->AssetRegistry = &FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
this->OnAssetAddedHandle = this->AssetRegistry->OnAssetAdded().AddRaw(this, &FComboActionSearchManager::HandleOnAssetAdded);
this->OnAssetRemovedHandle = this->AssetRegistry->OnAssetRemoved().AddRaw(this, &FComboActionSearchManager::HandleOnAssetRemoved);
this->OnAssetRenamedHandle = this->AssetRegistry->OnAssetRenamed().AddRaw(this, &FComboActionSearchManager::HandleOnAssetRenamed);
if (this->AssetRegistry->IsLoadingAssets())
{
this->OnFilesLoadedHandle = this->AssetRegistry->OnFilesLoaded().AddRaw(this, &FComboActionSearchManager::HandleOnAssetRegistryFilesLoaded);
}
else
{
this->HandleOnAssetRegistryFilesLoaded();
}
this->OnAssetLoadedHandle = FCoreUObjectDelegates::OnAssetLoaded.AddRaw(this, &FComboActionSearchManager::HandleOnAssetLoaded);
}
void FComboActionSearchManager::UnInitialize()
{
if (this->AssetRegistry)
{
if (this->OnAssetAddedHandle.IsValid())
{
this->AssetRegistry->OnAssetAdded().Remove(this->OnAssetAddedHandle);
this->OnAssetAddedHandle.Reset();
}
if (this->OnAssetRemovedHandle.IsValid())
{
this->AssetRegistry->OnAssetRemoved().Remove(this->OnAssetRemovedHandle);
this->OnAssetRemovedHandle.Reset();
}
if (this->OnFilesLoadedHandle.IsValid())
{
this->AssetRegistry->OnFilesLoaded().Remove(this->OnFilesLoadedHandle);
this->OnFilesLoadedHandle.Reset();
}
if (this->OnAssetRenamedHandle.IsValid())
{
this->AssetRegistry->OnAssetRenamed().Remove(this->OnAssetRenamedHandle);
this->OnAssetRenamedHandle.Reset();
}
}
if (this->OnAssetLoadedHandle.IsValid())
{
FCoreUObjectDelegates::OnAssetLoaded.Remove(this->OnAssetLoadedHandle);
this->OnAssetLoadedHandle.Reset();
}
}
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,113 @@
// Copyright Dominik Pavlicek 2023. All Rights Reserved.
#pragma once
#include "Search/ComboActionSearchResult.h"
#include "ComboActionGraph.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Ed/EdComboActionGraphNode.h"
struct FDialogueSearchData
{
TWeakObjectPtr<class UComboActionGraph> Dialogue;
};
class FComboActionSearchManager
{
public:
static FComboActionSearchManager *Get()
{
if (Instance == nullptr)
{
Instance = new FComboActionSearchManager();
}
return Instance;
}
public:
FComboActionSearchManager(){}
~FComboActionSearchManager() { this->UnInitialize(); }
/**
* Searches for InSearchString in the InGraphNode. Adds the result as a child in OutParentNode.
* @return True if found anything matching the InSearchString
*/
bool QueryGraphNode(const FComboActionSearchFilter &SearchFilter, const UEdComboActionGraphNode *InGraphNode, const TSharedPtr<class FComboActionSearchResult> &OutParentNode) const;
bool QueryNodeDecorators
(
const FComboActionSearchFilter &SearchFilter,
const FComboActionDecorator &InDecorator,
const TSharedPtr<class FComboActionSearchResult> &OutParentNode,
int32 DecoratorIndex,
FName DecoratorMemberName
) const;
/**
* Searches for InSearchString in the InAction. Adds the result as a child of OutParentNode.
* @return True if found anything matching the InSearchString
*/
bool QuerySingleAction
(
const FComboActionSearchFilter &SearchFilter,
const UComboActionGraph *InAction,
TSharedPtr<class FComboActionSearchResult> &OutParentNode
);
void Initialize(TSharedPtr<FWorkspaceItem> ParentTabCategory = nullptr);
void UnInitialize();
private:
// Helper method to make a Text Node and add it as a child to ParentNode
TSharedPtr<FComboActionSearchResult> MakeChildTextNode
(
const TSharedPtr<FComboActionSearchResult> &ParentNode,
const FText &DisplayName, const FText &Category,
const FString &CommentString
) const
{
TSharedPtr<FComboActionSearchResult> TextNode = MakeShared<FComboActionSearchResult>(DisplayName, ParentNode);
TextNode->SetCategory(Category);
if (!CommentString.IsEmpty())
{
TextNode->SetCommentString(CommentString);
}
ParentNode->AddChild(TextNode);
return TextNode;
}
// Callback hook from the Asset Registry when an asset is added
void HandleOnAssetAdded(const FAssetData &InAssetData){}
// Callback hook from the Asset Registry, marks the asset for deletion from the cache
void HandleOnAssetRemoved(const FAssetData &InAssetData){}
// Callback hook from the Asset Registry, marks the asset for deletion from the cache
void HandleOnAssetRenamed(const FAssetData &InAssetData, const FString &InOldName){}
// Callback hook from the Asset Registry when an asset is loaded
void HandleOnAssetLoaded(UObject *InAsset){}
// Callback when the Asset Registry loads all its assets
void HandleOnAssetRegistryFilesLoaded(){}
private:
static FComboActionSearchManager *Instance;
// Maps the Dialogue path => SearchData.
TMap<FName, FDialogueSearchData> SearchMap;
// Because we are unable to query for the module on another thread, cache it for use later
IAssetRegistry *AssetRegistry = nullptr;
// Handlers
FDelegateHandle OnAssetAddedHandle;
FDelegateHandle OnAssetRemovedHandle;
FDelegateHandle OnAssetRenamedHandle;
FDelegateHandle OnFilesLoadedHandle;
FDelegateHandle OnAssetLoadedHandle;
};
@@ -0,0 +1,73 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionSearchResult.h"
#include "ComboActionGraph.h"
#include "ComboActionSearchUtils.h"
#include "Helpers/ComboActionGraphEditorUtilities.h"
#define LOCTEXT_NAMESPACE "ComboActionSearchResult"
TSharedRef<SWidget> FComboActionSearchResult::CreateIcon() const
{
const FLinearColor IconColor = FLinearColor::White;
const FSlateBrush *Brush = nullptr;
return SNew(SImage)
.Image(Brush)
.ColorAndOpacity(IconColor)
.ToolTipText(GetCategory());
}
TWeakObjectPtr<const UComboActionGraph> FComboActionSearchResult::GetParentDialogue() const
{
if (this->Parent.IsValid())
{
return this->Parent.Pin()->GetParentDialogue();
}
return nullptr;
}
#pragma region Search_Actions
TWeakObjectPtr<const UComboActionGraph> FComboActionSearchResult_ActionNode::GetParentDialogue() const
{
if (this->ComboActionGraph.IsValid())
{
return this->ComboActionGraph;
}
return FComboActionSearchResult::GetParentDialogue();
}
#pragma endregion
#pragma region Search_Nodes
FReply FComboActionSearchResult_GraphNode::OnClick(TWeakPtr<FAssetEditor_ComboActionGraph> ActionEditorPtr)
{
if (this->GraphNode.IsValid())
{
return FComboActionGraphEditorUtilities::OpenEditorAndJumpToGraphNode(ActionEditorPtr, this->GraphNode.Get()) ? FReply::Handled() : FReply::Unhandled();
}
return FReply::Unhandled();
}
TSharedRef<SWidget> FComboActionSearchResult_GraphNode::CreateIcon() const
{
if (this->GraphNode.IsValid())
{
FLinearColor Color;
const FSlateIcon Icon = this->GraphNode.Get()->GetIconAndTint(Color);
return SNew(SImage)
.Image(Icon.GetOptionalIcon())
.ColorAndOpacity(Color)
.ToolTipText(GetCategory());
}
return FComboActionSearchResult::CreateIcon();
}
#pragma endregion
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,211 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Ed/EdComboActionGraph.h"
#include "Ed/EdComboActionGraphNode.h"
#include "Widgets/Views/STreeView.h"
#define LOCTEXT_NAMESPACE "ComboActionSearchResult"
/**
* Template Tree Item Node class.
*/
template <class SelfType>
class FComboActionTreeItemNode : public TSharedFromThis<SelfType>
{
public:
FComboActionTreeItemNode(const FText &InDisplayText, const TSharedPtr<SelfType> &InParent) : Parent(InParent), DisplayText(InDisplayText){};
virtual ~FComboActionTreeItemNode(){};
#pragma region Click_Functions
virtual FReply OnClick(TWeakPtr<FAssetEditor_ComboActionGraph> DialogueEditorPtr)
{
// If there is a parent, handle it using the parent's functionality
if (Parent.IsValid())
{
return Parent.Pin()->OnClick(DialogueEditorPtr);
}
return FReply::Unhandled();
}
#pragma endregion
#pragma region DisplayText_Functions
FText GetDisplayText() const { return this->DisplayText; }
FName GetDisplayTextAsFName() const { return FName(*this->DisplayText.ToString()); }
void SetDisplayText(const FText &InText) { this->DisplayText = InText; }
bool DoesDisplayTextContains(const FString &InSearch, ESearchCase::Type SearchCase = ESearchCase::IgnoreCase) const
{
return this->DisplayText.ToString().Contains(InSearch, SearchCase);
}
#pragma endregion
#pragma region Parent_Functions
bool HasParent() const { return this->Parent.IsValid(); }
TWeakPtr<SelfType> GetParent() const { return this->Parent; }
void SetParent(TWeakPtr<SelfType> InParentNode) { this->Parent = InParentNode; }
void ClearParent() { this->Parent.Reset(); }
#pragma endregion
#pragma region Children_Functions
public:
bool HasChildren() const { return this->Children.Num() > 0; }
const TArray<TSharedPtr<SelfType>> &GetChildren() const { return this->Children; }
void GetVisibleChildren(TArray<TSharedPtr<SelfType>> &OutChildren)
{
for (const TSharedPtr<SelfType> &Child : this->Children)
{
if (Child->IsVisible())
{
OutChildren.Add(Child);
}
}
}
virtual void AddChild(const TSharedPtr<SelfType> &ChildNode)
{
ensure(!ChildNode->IsRoot());
ChildNode->SetParent(this->AsShared());
this->Children.Add(ChildNode);
}
virtual void SetChildren(const TArray<TSharedPtr<SelfType>> &InChildren)
{
this->Children = InChildren;
for (const TSharedPtr<SelfType> &Child : this->Children)
{
ensure(!Child->IsRoot());
Child->SetParent(this->AsShared());
}
}
virtual void ClearChildren()
{
this->Children.Empty();
}
void ExpandAllChildren(const TSharedPtr<STreeView<TSharedPtr<SelfType>>> &TreeView, bool bRecursive = true)
{
static constexpr bool bShouldExpandItem = true;
if (!HasChildren())
{
return;
}
TreeView->SetItemExpansion(this->AsShared(), bShouldExpandItem);
for (const TSharedPtr<SelfType> &ChildNode : this->Children)
{
if (bRecursive)
{
// recursive on all children.
ChildNode->ExpandAllChildren(TreeView, bRecursive);
}
else
{
// Only direct children
TreeView->SetItemExpansion(ChildNode, bShouldExpandItem);
}
}
}
#pragma endregion
#pragma region Helper_Functions
public:
bool IsRoot() const { return !this->Parent.IsValid(); }
#pragma endregion
protected:
/** Any children listed under this node. */
TArray<TSharedPtr<SelfType>> Children;
/** The node that this is a direct child of (empty if this is a root node) */
TWeakPtr<SelfType> Parent;
/** The displayed text for this item. */
FText DisplayText;
/** Is this node displayed? */
bool bIsVisible = true;
};
class FComboActionSearchResult : public FComboActionTreeItemNode<FComboActionSearchResult>
{
public:
FComboActionSearchResult(const FText &InDisplayText, const TSharedPtr<FComboActionSearchResult> &InParent) : FComboActionTreeItemNode(InDisplayText, InParent){}
public:
// Create an icon to represent the result
virtual TSharedRef<SWidget> CreateIcon() const;
// Gets the Dialogue housing all these search results. Aka the Dialogue this search result belongs to.
virtual TWeakObjectPtr<const UComboActionGraph> GetParentDialogue() const;
// Category:
FText GetCategory() const { return this->Category; }
void SetCategory(const FText &InCategory) { this->Category = InCategory; }
// CommentString
FString GetCommentString() const { return this->CommentString; }
void SetCommentString(const FString &InCommentString) { this->CommentString = InCommentString; }
protected:
// The category of this node.
FText Category;
// Display text for comment information
FString CommentString;
};
// Root Node, should not be displayed.
class FComboActionSearchResult_RootNode : public FComboActionSearchResult
{
public:
FComboActionSearchResult_RootNode() : FComboActionSearchResult(FText::FromString(TEXT("INVALID")), nullptr) { this->Category = LOCTEXT("ComboActionSearchResult_RootNodeCategory", "Root"); }
};
// Tree Node result that represents the Node
class FComboActionSearchResult_ActionNode : public FComboActionSearchResult
{
public:
FComboActionSearchResult_ActionNode(const FText &InDisplayText, const TSharedPtr<FComboActionSearchResult> &InParent) : FComboActionSearchResult(InDisplayText, InParent) { this->Category = LOCTEXT("ComboAcitonSearchResult_ActionNodeCategory", "Action Node"); }
virtual FReply OnClick(TWeakPtr<class FAssetEditor_ComboActionGraph> DialogueEditorPtr) override { return FReply::Unhandled(); }
virtual TSharedRef<SWidget> CreateIcon() const override { return FComboActionSearchResult::CreateIcon(); }
virtual TWeakObjectPtr<const UComboActionGraph> GetParentDialogue() const override;
void SetActionGraph(TWeakObjectPtr<const UEdComboActionGraph> InDialogueGraph) { this->ComboActionGraph = InDialogueGraph->GetComboActionGraph(); }
protected:
TWeakObjectPtr<const UComboActionGraph> ComboActionGraph;
};
// Tree Node result that represents the GraphNode
class FComboActionSearchResult_GraphNode : public FComboActionSearchResult
{
public:
FComboActionSearchResult_GraphNode(const FText &InDisplayText, const TSharedPtr<FComboActionSearchResult> &InParent) : FComboActionSearchResult(InDisplayText, InParent){}
virtual FReply OnClick(TWeakPtr<FAssetEditor_ComboActionGraph> ComboActionEditorPtr) override;
virtual TSharedRef<SWidget> CreateIcon() const override;
void SetGraphNode(TWeakObjectPtr<const UEdComboActionGraphNode> InGraphNode) { this->GraphNode = InGraphNode; }
protected:
TWeakObjectPtr<const UEdComboActionGraphNode> GraphNode;
};
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,13 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#include "ComboActionSearchUtils.h"
TSharedPtr<SDockTab> FComboActionSearchHelpers::InvokeTab(TSharedPtr<FTabManager> TabManager, const FTabId &TabID)
{
if (!TabManager.IsValid())
{
return nullptr;
}
return TabManager->TryInvokeTab(TabID);
}
@@ -0,0 +1,10 @@
// ©2023 Batty Bovine Productions, LLC. All Rights Reserved.
#pragma once
class COMBOINPUTEDITOR_API FComboActionSearchHelpers
{
public:
static TSharedPtr<SDockTab> InvokeTab(TSharedPtr<FTabManager> TabManager, const FTabId &TabID);
};
@@ -0,0 +1,405 @@
// Copyright Dominik Pavlicek 2023. All Rights Reserved.
#include "SComboActionSearch.h"
#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Framework/Commands/GenericCommands.h"
#include "Search/ComboActionSearchManager.h"
#include "Widgets/Input/SSearchBox.h"
#define LOCTEXT_NAMESPACE "SComboActionSearch"
void SComboActionSearch::Construct(const FArguments &InArgs, const TSharedPtr<FAssetEditor_ComboActionGraph> &InDialogueEditor)
{
this->ComboActionEditorPtr = InDialogueEditor;
this->HostTab = InArgs._ContainingTab;
if (this->HostTab.IsValid())
{
this->HostTab.Pin()->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateSP(this, &SComboActionSearch::HandleHostTabClosed));
}
this->ChildSlot
[
SAssignNew(this->MainVerticalBoxWidget, SVerticalBox)
// Top bar, search
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
// Search field
+SHorizontalBox::Slot()
.FillWidth(1)
[
SAssignNew(this->SearchTextBoxWidget, SSearchBox)
.HintText(LOCTEXT("ActionSearchHint", "Enter searched text..."))
.OnTextChanged(this, &SComboActionSearch::HandleSearchTextChanged)
.OnTextCommitted(this, &SComboActionSearch::HandleSearchTextCommitted)
.Visibility(EVisibility::Visible)
]
// Filter Options
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f, 2.0f)
[
SNew(SComboButton)
.ComboButtonStyle(FAppStyle::Get(), "GenericFilters.ComboButtonStyle")
.ForegroundColor(FLinearColor::White)
.ContentPadding(0)
.ToolTipText(LOCTEXT("Filters_Tooltip", "Filter options"))
.OnGetMenuContent(this, &SComboActionSearch::FillFilterEntries)
.HasDownArrow(true)
.ContentPadding(FMargin(1, 0))
.ButtonContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "GenericFilters.TextStyle")
.Font(FAppStyle::Get().GetFontStyle("FontAwesome.9"))
.Text(FText::FromString(FString(TEXT("\xf0b0"))) )
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0, 0, 0)
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "GenericFilters.TextStyle")
.Text(LOCTEXT("Filters", "Filters"))
]
]
]
]
// Results tree
+SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(0.0f, 4.0f, 0.0f, 0.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Menu.Background"))
[
SAssignNew(this->TreeView, STreeView<TSharedPtr<FComboActionSearchResult>>)
.ItemHeight(24)
.TreeItemsSource(&this->ItemsFound)
.OnGenerateRow(this, &SComboActionSearch::HandleGenerateRow)
.OnGetChildren(this, &SComboActionSearch::HandleGetChildren)
.OnMouseButtonDoubleClick(this, &SComboActionSearch::HandleTreeSelectionDoubleClicked)
.SelectionMode(ESelectionMode::Multi)
.OnContextMenuOpening(this, &SComboActionSearch::HandleContextMenuOpening)
]
]
];
}
void SComboActionSearch::FocusForUse(const FComboActionSearchFilter &SearchFilter, bool bSelectFirstResult)
{
FWidgetPath FilterTextBoxWidgetPath;
FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchTextBoxWidget.ToSharedRef(), FilterTextBoxWidgetPath);
FSlateApplication::Get().SetKeyboardFocus(FilterTextBoxWidgetPath, EFocusCause::SetDirectly);
if (!SearchFilter.SearchString.IsEmpty())
{
SearchTextBoxWidget->SetText(FText::FromString(SearchFilter.SearchString));
MakeSearchQuery(SearchFilter);
if (bSelectFirstResult && this->ItemsFound.Num())
{
auto ItemToFocusOn = this->ItemsFound[0];
while (ItemToFocusOn->HasChildren())
{
ItemToFocusOn = ItemToFocusOn->GetChildren()[0];
}
this->TreeView->SetSelection(ItemToFocusOn);
ItemToFocusOn->OnClick(this->ComboActionEditorPtr);
}
}
}
void SComboActionSearch::MakeSearchQuery(const FComboActionSearchFilter &SearchFilter)
{
this->SearchTextBoxWidget->SetText(FText::FromString(SearchFilter.SearchString));
if (this->ItemsFound.Num())
{
this->TreeView->RequestScrollIntoView(this->ItemsFound[0]);
}
this->ItemsFound.Empty();
if (SearchFilter.SearchString.IsEmpty())
{
return;
}
this->HighlightText = FText::FromString(SearchFilter.SearchString);
this->RootSearchResult = MakeShared<FComboActionSearchResult_RootNode>();
if (this->ComboActionEditorPtr.IsValid())
{
FComboActionSearchManager::Get()->QuerySingleAction(SearchFilter, this->ComboActionEditorPtr.Pin()->GetEditingGraphSafe(), this->RootSearchResult);
const TArray<TSharedPtr<FComboActionSearchResult>> &Children = this->RootSearchResult->GetChildren();
if (Children.Num() == 1 && Children[0].IsValid())
{
// we must ensure reference is created so its not garbage collected, usually resulting in crash!
TSharedPtr<FComboActionSearchResult> TempChild = Children[0];
this->RootSearchResult = TempChild;
this->RootSearchResult->ClearParent();
}
}
this->ItemsFound = this->RootSearchResult->GetChildren();
if (this->ItemsFound.Num() == 0)
{
this->ItemsFound.Add(MakeShared<FComboActionSearchResult>(LOCTEXT("ActionSearchNoResults", "No Results found"), this->RootSearchResult));
this->HighlightText = FText::GetEmpty();\
}
else
{
this->RootSearchResult->ExpandAllChildren(this->TreeView);
}
this->TreeView->RequestTreeRefresh();
}
FName SComboActionSearch::GetHostTabId() const
{
const TSharedPtr<SDockTab> HostTabPtr = this->HostTab.Pin();
if (HostTabPtr.IsValid())
{
return HostTabPtr->GetLayoutIdentifier().TabType;
}
return NAME_None;
}
void SComboActionSearch::CloseHostTab()
{
const TSharedPtr<SDockTab> HostTabPtr = this->HostTab.Pin();
if (HostTabPtr.IsValid())
{
HostTabPtr->RequestCloseTab();
}
}
void SComboActionSearch::HandleHostTabClosed(TSharedRef<SDockTab> DockTab)
{
// Clear cache
}
void SComboActionSearch::HandleSearchTextChanged(const FText& Text)
{
this->CurrentFilter.SearchString = Text.ToString();
}
void SComboActionSearch::HandleSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType)
{
if (Text.IsEmpty())
{
this->TreeView->RequestTreeRefresh();
}
if (CommitType == ETextCommit::OnEnter)
{
this->CurrentFilter.SearchString = Text.ToString();
MakeSearchQuery(this->CurrentFilter);
}
}
void SComboActionSearch::HandleGetChildren(TSharedPtr<FComboActionSearchResult> InItem, TArray<TSharedPtr<FComboActionSearchResult>> &OutChildren)
{
OutChildren += InItem->GetChildren();
}
void SComboActionSearch::HandleTreeSelectionDoubleClicked(TSharedPtr<FComboActionSearchResult> Item)
{
if (Item.IsValid())
{
Item->OnClick(this->ComboActionEditorPtr);
}
}
TSharedRef<ITableRow> SComboActionSearch::HandleGenerateRow(TSharedPtr<FComboActionSearchResult> InItem, const TSharedRef<STableViewBase> &OwnerTable)
{
// Normal entry
FText CommentText = FText::GetEmpty();
if (!InItem->GetCommentString().IsEmpty())
{
FFormatNamedArguments Args;
Args.Add(TEXT("Comment"), FText::FromString(InItem->GetCommentString()));
CommentText = FText::Format(LOCTEXT("NodeComment", "{Comment}"), Args);
}
FFormatNamedArguments Args;
Args.Add(TEXT("Category"), InItem->GetCategory());
Args.Add(TEXT("DisplayTitle"), InItem->GetDisplayText());
const FText Tooltip = FText::Format(LOCTEXT("DialogueResultSearchToolTip", "{Category} : {DisplayTitle}"), Args);
return SNew(STableRow<TSharedPtr<FComboActionSearchResult>>, OwnerTable)
[
SNew(SHorizontalBox)
// Icon
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
InItem->CreateIcon()
]
// Display text
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(2,0)
[
SNew(STextBlock)
.Text(InItem.Get(), &FComboActionSearchResult::GetDisplayText)
.HighlightText(HighlightText)
.ToolTipText(Tooltip)
]
// Comment Block
+SHorizontalBox::Slot()
.FillWidth(1)
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(2,0)
[
SNew(STextBlock)
.Text(CommentText)
.ColorAndOpacity(FLinearColor::Yellow)
]
];
}
TSharedPtr<SWidget> SComboActionSearch::HandleContextMenuOpening()
{
const bool bShouldCloseWindowAfterMenuSelection = true;
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, this->CommandList);
MenuBuilder.BeginSection("BasicOperations");
{
MenuBuilder.AddMenuEntry(FGenericCommands::Get().SelectAll);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy);
}
return MenuBuilder.MakeWidget();
}
TSharedRef<SWidget> SComboActionSearch::FillFilterEntries()
{
FMenuBuilder MenuBuilder(true, nullptr);
MenuBuilder.AddMenuEntry
(
LOCTEXT("IncludeNodeTitle", "Include Node Title"),
LOCTEXT("IncludeNodeTitle_ToolTip", "Include Node Titles in the search result"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
this->CurrentFilter.bIncludeNodeTitle = !this->CurrentFilter.bIncludeNodeTitle;
MakeSearchQuery(this->CurrentFilter);
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([this]() -> bool
{
return this->CurrentFilter.bIncludeNodeTitle;
})
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry
(
LOCTEXT("IncludeNodeType", "Include Node Type"),
LOCTEXT("IncludeNodeType_ToolTip", "Include Node Type in the search result"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
this->CurrentFilter.bIncludeNodeType = !this->CurrentFilter.bIncludeNodeType;
MakeSearchQuery(this->CurrentFilter);
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([this]() -> bool
{
return this->CurrentFilter.bIncludeNodeType;
})
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry
(
LOCTEXT("IncludeNodeDecoratorsTypes", "Include Node Decorators"),
LOCTEXT("IncludeNodeDecoratorsTypes_ToolTip", "Include Node Decorators Types (by name) in the search result"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
this->CurrentFilter.bIncludeNodeDecoratorsTypes = !this->CurrentFilter.bIncludeNodeDecoratorsTypes;
MakeSearchQuery(this->CurrentFilter);
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([this]() -> bool
{
return this->CurrentFilter.bIncludeNodeDecoratorsTypes;
})
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry
(
LOCTEXT("IncludeNodeData", "Include Node Data Row"),
LOCTEXT("IncludeNodeDecoratorsTypes_ToolTip", "Include Node Data Row in the search result"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
this->CurrentFilter.bIncludeNodeData = !this->CurrentFilter.bIncludeNodeData;
MakeSearchQuery(this->CurrentFilter);
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([this]() -> bool
{
return this->CurrentFilter.bIncludeNodeData;
})
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
MenuBuilder.AddMenuEntry
(
LOCTEXT("IncludeNodeGUID", "Include Node GUID"),
LOCTEXT("IncludeNodeGUID_ToolTip", "Include Node GUID in the search result"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
this->CurrentFilter.bIncludeNodeGUID = !this->CurrentFilter.bIncludeNodeGUID;
MakeSearchQuery(this->CurrentFilter);
}),
FCanExecuteAction(),
FIsActionChecked::CreateLambda([this]() -> bool
{
return this->CurrentFilter.bIncludeNodeGUID;
})
),
NAME_None,
EUserInterfaceActionType::ToggleButton
);
return MenuBuilder.MakeWidget();
}
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,106 @@
// Copyright Dominik Pavlicek 2023. All Rights Reserved.
#pragma once
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STreeView.h"
#include "Framework/Commands/UICommandList.h"
#include "Search/ComboActionSearchFilter.h"
#include "Search/ComboActionSearchResult.h"
/**
* Widget handling Search in Combo Action Graph
*/
class SComboActionSearch : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SComboActionSearch)
: _bIsSearchWindow(true)
, _bHideSearchBar(false)
, _ContainingTab()
{}
SLATE_ARGUMENT(bool, bIsSearchWindow)
SLATE_ARGUMENT(bool, bHideSearchBar)
SLATE_ARGUMENT(TSharedPtr<SDockTab>, ContainingTab)
SLATE_END_ARGS()
void Construct(const FArguments &InArgs, const TSharedPtr<FAssetEditor_ComboActionGraph> &InDialogueEditor = nullptr);
/** Focuses this widget's search box, and changes the mode as well, and optionally the search terms */
void FocusForUse(const FComboActionSearchFilter &SearchFilter = FComboActionSearchFilter(), bool bSelectFirstResult = false);
/**
* Submits a search query
*
* @param SearchFilter Filter for search
* @param bInIsFindWithinDialogue TRUE if searching within the current Dialogue only
*/
void MakeSearchQuery(const FComboActionSearchFilter &SearchFilter);
/** If this is a global find results widget, returns the host tab's unique ID. Otherwise, returns NAME_None. */
FName GetHostTabId() const;
/** If this is a global find results widget, ask the host tab to close */
void CloseHostTab();
private:
/** Called when the host tab is closed (if valid) */
void HandleHostTabClosed(TSharedRef<SDockTab> DockTab);
/** Called when user changes the text they are searching for */
void HandleSearchTextChanged(const FText &Text);
/** Called when user changes commits text to the search box */
void HandleSearchTextCommitted(const FText &Text, ETextCommit::Type CommitType);
/* Get the children of a row */
void HandleGetChildren(TSharedPtr<FComboActionSearchResult> InItem, TArray<TSharedPtr<FComboActionSearchResult>> &OutChildren);
/* Called when user double clicks on a new result */
void HandleTreeSelectionDoubleClicked(TSharedPtr<FComboActionSearchResult> Item);
/* Called when a new row is being generated */
TSharedRef<ITableRow> HandleGenerateRow(TSharedPtr<FComboActionSearchResult> InItem, const TSharedRef<STableViewBase> &OwnerTable);
/** Callback to build the context menu when right clicking in the tree */
TSharedPtr<SWidget> HandleContextMenuOpening();
/** Fills in the filter menu. */
TSharedRef<SWidget> FillFilterEntries();
private:
/** Pointer back to the ComboAction editor that owns us */
TWeakPtr<FAssetEditor_ComboActionGraph> ComboActionEditorPtr;
/* The tree view displays the results */
TSharedPtr<STreeView<TSharedPtr<FComboActionSearchResult>>> TreeView;
/** The search text box */
TSharedPtr<SSearchBox> SearchTextBoxWidget;
/** Vertical box, used to add and remove widgets dynamically */
TWeakPtr<SVerticalBox> MainVerticalBoxWidget;
/** In Find Within Action mode, we need to keep a handle on the root result, because it won't show up in the tree. */
TSharedPtr<FComboActionSearchResult> RootSearchResult;
/* This buffer stores the currently displayed results */
TArray<TSharedPtr<FComboActionSearchResult>> ItemsFound;
/* The string to highlight in the results */
FText HighlightText;
/** The current searach filter */
FComboActionSearchFilter CurrentFilter;
/** Tab hosting this widget. May be invalid. */
TWeakPtr<SDockTab> HostTab;
/** Commands handled by this widget */
TSharedPtr<FUICommandList> CommandList;
};