Files
UnrealEngine/Engine/Plugins/Experimental/CommonConversation/Source/CommonConversationEditor/Private/FindInConversationGraph.cpp
2025-05-18 13:04:45 +08:00

354 lines
9.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FindInConversationGraph.h"
#include "ConversationEditor.h"
#include "EdGraph/EdGraph.h"
#include "Framework/Views/TableViewMetadata.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "ConversationGraphNode.h"
//#include "ConversationGraphNode_Decorator.h"
//#include "ConversationGraphNode_Service.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Views/STreeView.h"
#define LOCTEXT_NAMESPACE "FindInBT"
//////////////////////////////////////////////////////////////////////////
// FFindInConversationResult
FFindInConversationResult::FFindInConversationResult(const FString& InValue)
: Value(InValue), GraphNode(NULL)
{
}
FFindInConversationResult::FFindInConversationResult(const FString& InValue, TSharedPtr<FFindInConversationResult>& InParent, UEdGraphNode* InNode)
: Value(InValue), GraphNode(InNode), Parent(InParent)
{
}
void FFindInConversationResult::SetNodeHighlight(bool bHighlight)
{
// if (GraphNode.IsValid())
// {
// UConversationGraphNode* BTNode = Cast<UConversationGraphNode>(GraphNode.Get());
// if (BTNode)
// {
// BTNode->bHighlightInSearchTree = bHighlight;
// }
// }
}
TSharedRef<SWidget> FFindInConversationResult::CreateIcon() const
{
FSlateColor IconColor = FSlateColor::UseForeground();
const FSlateBrush* Brush = NULL;
if (GraphNode.IsValid())
{
// if (Cast<UConversationGraphNode_Service>(GraphNode.Get()))
// {
// Brush = FAppStyle::GetBrush(TEXT("GraphEditor.PinIcon"));
// }
// else
// if (Cast<UConversationGraphNode_Decorator>(GraphNode.Get()))
// {
// Brush = FAppStyle::GetBrush(TEXT("GraphEditor.RefPinIcon"));
// }
// else
{
Brush = FAppStyle::GetBrush(TEXT("GraphEditor.FIB_Event"));
}
}
return SNew(SImage)
.Image(Brush)
.ColorAndOpacity(IconColor);
}
FReply FFindInConversationResult::OnClick(TWeakPtr<class FConversationEditor> ConversationEditorPtr, TSharedPtr<FFindInConversationResult> Root)
{
if (ConversationEditorPtr.IsValid() && GraphNode.IsValid())
{
if (Parent.IsValid() && Parent.HasSameObject(Root.Get()))
{
ConversationEditorPtr.Pin()->JumpToNode(GraphNode.Get());
}
else
{
ConversationEditorPtr.Pin()->JumpToNode(Parent.Pin()->GraphNode.Get());
}
}
return FReply::Handled();
}
FString FFindInConversationResult::GetNodeTypeText() const
{
if (GraphNode.IsValid())
{
FString NodeClassName = GraphNode->GetClass()->GetName();
int32 Pos = NodeClassName.Find("_");
if (Pos == INDEX_NONE)
{
return NodeClassName;
}
else
{
return NodeClassName.RightChop(Pos + 1);
}
}
return FString();
}
FString FFindInConversationResult::GetCommentText() const
{
if (GraphNode.IsValid())
{
return GraphNode->NodeComment;
}
return FString();
}
//////////////////////////////////////////////////////////////////////////
// SFindInConversation
void SFindInConversation::Construct( const FArguments& InArgs, TSharedPtr<FConversationEditor> InConversationEditor)
{
ConversationEditorPtr = InConversationEditor;
this->ChildSlot
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1)
[
SAssignNew(SearchTextField, SSearchBox)
.HintText(LOCTEXT("ConversationSearchHint", "Enter text to find nodes..."))
.OnTextChanged(this, &SFindInConversation::OnSearchTextChanged)
.OnTextCommitted(this, &SFindInConversation::OnSearchTextCommitted)
]
]
+SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(0.f, 4.f, 0.f, 0.f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Menu.Background"))
[
SAssignNew(TreeView, STreeViewType)
.TreeItemsSource(&ItemsFound)
.OnGenerateRow(this, &SFindInConversation::OnGenerateRow)
.OnGetChildren(this, &SFindInConversation::OnGetChildren)
.OnSelectionChanged(this, &SFindInConversation::OnTreeSelectionChanged)
.SelectionMode(ESelectionMode::Multi)
]
]
];
}
void SFindInConversation::FocusForUse()
{
// NOTE: Careful, GeneratePathToWidget can be reentrant in that it can call visibility delegates and such
FWidgetPath FilterTextBoxWidgetPath;
FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchTextField.ToSharedRef(), FilterTextBoxWidgetPath);
// Set keyboard focus directly
FSlateApplication::Get().SetKeyboardFocus(FilterTextBoxWidgetPath, EFocusCause::SetDirectly);
}
void SFindInConversation::OnSearchTextChanged(const FText& Text)
{
SearchValue = Text.ToString();
InitiateSearch();
}
void SFindInConversation::OnSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType)
{
OnSearchTextChanged(Text);
}
void SFindInConversation::InitiateSearch()
{
TArray<FString> Tokens;
SearchValue.ParseIntoArray(Tokens, TEXT(" "), true);
for (auto It(ItemsFound.CreateIterator()); It; ++It)
{
(*It).Get()->SetNodeHighlight(false); // need to reset highlight
TreeView->SetItemExpansion(*It, false);
}
ItemsFound.Empty();
if (Tokens.Num() > 0)
{
HighlightText = FText::FromString(SearchValue);
MatchTokens(Tokens);
}
// Insert a fake result to inform user if none found
if (ItemsFound.Num() == 0)
{
ItemsFound.Add(FSearchResult(new FFindInConversationResult(LOCTEXT("ConversationSearchNoResults", "No Results found").ToString())));
}
TreeView->RequestTreeRefresh();
for (auto It(ItemsFound.CreateIterator()); It; ++It)
{
TreeView->SetItemExpansion(*It, true);
}
}
void SFindInConversation::MatchTokens(const TArray<FString>& Tokens)
{
RootSearchResult.Reset();
TWeakPtr<SGraphEditor> FocusedGraphEditor = ConversationEditorPtr.Pin()->GetFocusedGraphPtr();
UEdGraph* Graph = NULL;
if (FocusedGraphEditor.IsValid())
{
Graph = FocusedGraphEditor.Pin()->GetCurrentGraph();
}
if (Graph == NULL)
{
return;
}
RootSearchResult = FSearchResult(new FFindInConversationResult(FString("ConversationRoot")));
for (auto It(Graph->Nodes.CreateConstIterator()); It; ++It)
{
UEdGraphNode* Node = *It;
const FString NodeName = Node->GetNodeTitle(ENodeTitleType::ListView).ToString();
FSearchResult NodeResult(new FFindInConversationResult(NodeName, RootSearchResult, Node));
FString NodeSearchString = NodeName + Node->GetClass()->GetName() + Node->NodeComment;
NodeSearchString = NodeSearchString.Replace(TEXT(" "), TEXT(""));
bool bNodeMatchesSearch = StringMatchesSearchTokens(Tokens, NodeSearchString);
if (UConversationGraphNode* ConversationNode = Cast<UConversationGraphNode>(Node))
{
for (UAIGraphNode* SubNode : ConversationNode->SubNodes)
{
MatchTokensInChild(Tokens, SubNode, NodeResult);
}
}
if ((NodeResult->Children.Num() > 0) || bNodeMatchesSearch)
{
NodeResult->SetNodeHighlight(true);
ItemsFound.Add(NodeResult);
}
}
}
void SFindInConversation::MatchTokensInChild(const TArray<FString>& Tokens, UEdGraphNode* Child, FSearchResult ParentNode)
{
if (Child == NULL)
{
return;
}
FString ChildName = Child->GetNodeTitle(ENodeTitleType::ListView).ToString();
FString ChildSearchString = ChildName + Child->GetClass()->GetName() + Child->NodeComment;
ChildSearchString = ChildSearchString.Replace(TEXT(" "), TEXT(""));
if (StringMatchesSearchTokens(Tokens, ChildSearchString))
{
FSearchResult DecoratorResult(new FFindInConversationResult(ChildName, ParentNode, Child));
ParentNode->Children.Add(DecoratorResult);
}
}
TSharedRef<ITableRow> SFindInConversation::OnGenerateRow( FSearchResult InItem, const TSharedRef<STableViewBase>& OwnerTable )
{
return SNew(STableRow< TSharedPtr<FFindInConversationResult> >, OwnerTable)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SBox)
.WidthOverride(450.0f)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
[
InItem->CreateIcon()
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
.Padding(2, 0)
[
SNew(STextBlock)
.Text(FText::FromString(InItem->Value))
.HighlightText(HighlightText)
]
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(InItem->GetNodeTypeText()))
.HighlightText(HighlightText)
]
+SHorizontalBox::Slot()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(InItem->GetCommentText()))
.ColorAndOpacity(FLinearColor::Yellow)
.HighlightText(HighlightText)
]
];
}
void SFindInConversation::OnGetChildren(FSearchResult InItem, TArray< FSearchResult >& OutChildren)
{
OutChildren += InItem->Children;
}
void SFindInConversation::OnTreeSelectionChanged(FSearchResult Item , ESelectInfo::Type)
{
if (Item.IsValid())
{
Item->OnClick(ConversationEditorPtr, RootSearchResult);
}
}
bool SFindInConversation::StringMatchesSearchTokens(const TArray<FString>& Tokens, const FString& ComparisonString)
{
bool bFoundAllTokens = true;
//search the entry for each token, it must have all of them to pass
for (auto TokIT(Tokens.CreateConstIterator()); TokIT; ++TokIT)
{
const FString& Token = *TokIT;
if (!ComparisonString.Contains(Token))
{
bFoundAllTokens = false;
break;
}
}
return bFoundAllTokens;
}
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE