From 6d7ac95b2fdce4b304527876afd224ffdae624b0 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:57:16 -0800 Subject: [PATCH] Search improvements vs. flow mainline --- .../Nodes/Graph/FlowNode_FormatText.cpp | 6 +- .../Private/Asset/FlowObjectDiff.cpp | 2 +- Source/FlowEditor/Private/Asset/SFlowDiff.cpp | 51 +- Source/FlowEditor/Private/Find/FindInFlow.cpp | 1007 +++++++++++++---- .../Private/Find/SFindInFlowFilterPopup.cpp | 145 +++ .../FlowEditor/Private/FlowEditorModule.cpp | 41 + .../Private/Graph/Widgets/SFlowGraphNode.cpp | 4 +- Source/FlowEditor/Public/Find/FindInFlow.h | 150 ++- .../FlowEditor/Public/Find/FindInFlowEnums.h | 48 + .../Public/Find/SFindInFlowFilterPopup.h | 42 + Source/FlowEditor/Public/FlowEditorModule.h | 9 +- .../Public/Graph/FlowGraphEditorSettings.h | 10 + .../FlowEditor/Public/Graph/FlowGraphSchema.h | 3 + 13 files changed, 1210 insertions(+), 308 deletions(-) create mode 100644 Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp create mode 100644 Source/FlowEditor/Public/Find/FindInFlowEnums.h create mode 100644 Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp index e6564105d..13c055433 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_FormatText.cpp @@ -7,6 +7,8 @@ #define LOCTEXT_NAMESPACE "FlowNode_FormatText" +const FName UFlowNode_FormatText::OUTPIN_TextOutput("Formatted Text"); + UFlowNode_FormatText::UFlowNode_FormatText(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { @@ -15,12 +17,12 @@ UFlowNode_FormatText::UFlowNode_FormatText(const FObjectInitializer& ObjectIniti NodeDisplayStyle = FlowNodeStyle::Terminal; #endif - OutputPins.Add(FFlowPin(TEXT("Formatted Text"), FFlowPinType_Text::GetPinTypeNameStatic())); + OutputPins.Add(FFlowPin(OUTPIN_TextOutput, FFlowPinType_Text::GetPinTypeNameStatic())); } FFlowDataPinResult UFlowNode_FormatText::TrySupplyDataPin_Implementation(FName PinName) const { - if (PinName == TEXT("Formatted Text")) + if (PinName == OUTPIN_TextOutput) { FText FormattedText; const EFlowDataPinResolveResult FormatResult = TryResolveFormatText(PinName, FormattedText); diff --git a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp index 270eab9ac..dd94960d2 100644 --- a/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp +++ b/Source/FlowEditor/Private/Asset/FlowObjectDiff.cpp @@ -72,7 +72,7 @@ void FFlowObjectDiff::DiffProperties(TArray& OutProperty if (OldDetailsView.IsValid() && NewDetailsView.IsValid()) { static constexpr bool bSortByDisplayOrder = true; - //OldDetailsView->DiffAgainst(*NewDetailsView.Get(), OutPropertyDiffsArray, bSortByDisplayOrder); + OldDetailsView->DiffAgainst(*NewDetailsView.Get(), OutPropertyDiffsArray, bSortByDisplayOrder); } } diff --git a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp index 96540b6dc..2b1be2976 100644 --- a/Source/FlowEditor/Private/Asset/SFlowDiff.cpp +++ b/Source/FlowEditor/Private/Asset/SFlowDiff.cpp @@ -1,9 +1,10 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "Asset/SFlowDiff.h" -#include "Asset/FlowDiffControl.h" +#include "Asset/FlowDiffControl.h" #include "FlowAsset.h" +#include "Graph/Nodes/FlowGraphNode.h" #include "EdGraphUtilities.h" #include "Editor.h" @@ -38,11 +39,11 @@ static int32 GetCurrentIndex(SListView> const& Lis const TArray>& Selected = ListView.GetSelectedItems(); if (Selected.Num() == 1) { - for (const TSharedPtr& Diff : ListViewSource) + for (int32 Index = 0; Index < ListViewSource.Num(); ++Index) { - if (Diff == Selected[0]) + if (ListViewSource[Index] == Selected[0]) { - return 0; + return Index; } } } @@ -52,23 +53,21 @@ static int32 GetCurrentIndex(SListView> const& Lis void FlowDiffUtils::SelectNextRow(SListView>& ListView, const TArray>& ListViewSource) { const int32 CurrentIndex = GetCurrentIndex(ListView, ListViewSource); - if (CurrentIndex == ListViewSource.Num() - 1) + const int32 NextIndex = CurrentIndex + 1; + if (ListViewSource.IsValidIndex(NextIndex)) { - return; + ListView.SetSelection(ListViewSource[NextIndex]); } - - ListView.SetSelection(ListViewSource[CurrentIndex + 1]); } void FlowDiffUtils::SelectPrevRow(SListView>& ListView, const TArray>& ListViewSource) { const int32 CurrentIndex = GetCurrentIndex(ListView, ListViewSource); - if (CurrentIndex == 0) + const int32 PrevIndex = CurrentIndex - 1; + if (ListViewSource.IsValidIndex(PrevIndex)) { - return; + ListView.SetSelection(ListViewSource[PrevIndex]); } - - ListView.SetSelection(ListViewSource[CurrentIndex - 1]); } bool FlowDiffUtils::HasNextDifference(const SListView>& ListView, const TArray>& ListViewSource) @@ -538,16 +537,28 @@ void FFlowDiffPanel::GeneratePanel(UEdGraph* Graph, TSharedPtr(); - GraphEditorCommands->MapAction(FGenericCommands::Get().Copy, - FExecuteAction::CreateRaw(this, &FFlowDiffPanel::CopySelectedNodes), - FCanExecuteAction::CreateRaw(this, &FFlowDiffPanel::CanCopyNodes) + GraphEditorCommands->MapAction( + FGenericCommands::Get().Copy, + FExecuteAction::CreateRaw(this, &FFlowDiffPanel::CopySelectedNodes), + FCanExecuteAction::CreateRaw(this, &FFlowDiffPanel::CanCopyNodes) ); } @@ -683,14 +694,14 @@ void SFlowDiff::HandleGraphChanged(const FString& GraphPath) const TAttribute FocusedDiffResult = TAttribute::CreateLambda( [this, RealDifferencesStartIndex]() { - int32 FocusedDiffResult = INDEX_NONE; + int32 FocusedIndex = INDEX_NONE; if (RealDifferencesStartIndex != INDEX_NONE) { - FocusedDiffResult = DiffTreeView::CurrentDifference(DifferencesTreeView.ToSharedRef(), RealDifferences) - RealDifferencesStartIndex; + FocusedIndex = DiffTreeView::CurrentDifference(DifferencesTreeView.ToSharedRef(), RealDifferences) - RealDifferencesStartIndex; } // find selected index in all the graphs, and subtract the index of the first entry in this graph - return FocusedDiffResult; + return FocusedIndex; }); // only regenerate PanelOld if the old graph has changed @@ -934,4 +945,4 @@ void SFlowDiff::OnModeChanged(const FName& InNewViewMode) const UpdateTopSectionVisibility(InNewViewMode); } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/Find/FindInFlow.cpp b/Source/FlowEditor/Private/Find/FindInFlow.cpp index d098da8d5..648728c78 100644 --- a/Source/FlowEditor/Private/Find/FindInFlow.cpp +++ b/Source/FlowEditor/Private/Find/FindInFlow.cpp @@ -2,14 +2,20 @@ #include "Find/FindInFlow.h" #include "Asset/FlowAssetEditor.h" +#include "Find/SFindInFlowFilterPopup.h" #include "Graph/FlowGraphEditor.h" +#include "Graph/FlowGraphEditorSettings.h" #include "Graph/FlowGraphUtils.h" #include "Graph/Nodes/FlowGraphNode.h" - #include "FlowAsset.h" +#include "FlowEditorModule.h" #include "Nodes/FlowNode.h" +#include "Nodes/FlowNodeBase.h" +#include "AddOns/FlowNodeAddOn.h" #include "Nodes/Graph/FlowNode_SubGraph.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/ARFilter.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "Framework/Application/SlateApplication.h" @@ -22,16 +28,22 @@ #include "Layout/WidgetPath.h" #include "Math/Color.h" #include "Misc/Attribute.h" +#include "Misc/EnumRange.h" +#include "Misc/ScopedSlowTask.h" #include "SlotBase.h" +#include "Subsystems/AssetEditorSubsystem.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Casts.h" #include "Types/SlateStructs.h" #include "UObject/Class.h" #include "UObject/ObjectPtr.h" +#include "UObject/TopLevelAssetPath.h" #include "Widgets/Images/SImage.h" -#include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SSearchBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SSpinBox.h" +#include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" @@ -39,22 +51,58 @@ #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/STableRow.h" -class ITableRow; -class SWidget; -struct FSlateBrush; - #define LOCTEXT_NAMESPACE "FindInFlow" +////////////////////////////////////////////////////////////////////////// +// FFindInFlowCache + +TMap, TMap>> FFindInFlowCache::CategoryStringCache; + +void FFindInFlowCache::OnFlowAssetChanged(UFlowAsset& ChangedFlowAsset) +{ + TArray> EntriesToRemove; + + for (const auto& KV : CategoryStringCache) + { + const TWeakObjectPtr& EdNodePtr = KV.Key; + + UEdGraphNode* EdNode = EdNodePtr.Get(); + + if (!IsValid(EdNode)) + { + EntriesToRemove.Add(EdNodePtr); + + continue; + } + + UEdGraph* EdGraph = ChangedFlowAsset.GetGraph(); + if (EdGraph->Nodes.Contains(EdNode)) + { + EntriesToRemove.Add(EdNodePtr); + } + } + + for (const TWeakObjectPtr& EdNodePtr : EntriesToRemove) + { + CategoryStringCache.Remove(EdNodePtr); + } +} + ////////////////////////////////////////////////////////////////////////// // FFindInFlowResult -FFindInFlowResult::FFindInFlowResult(const FString& InValue) - : Value(InValue), GraphNode(nullptr) +FFindInFlowResult::FFindInFlowResult(const FString& InValue, UFlowAsset* InOwningFlowAsset) + : Value(InValue) + , OwningFlowAsset(InOwningFlowAsset) { } -FFindInFlowResult::FFindInFlowResult(const FString& InValue, TSharedPtr& InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode) - : Value(InValue), GraphNode(InNode), Parent(InParent), bIsSubGraphNode(bInIsSubGraphNode) +FFindInFlowResult::FFindInFlowResult(const FString& InValue, TSharedPtr InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode, UFlowAsset* InOwningFlowAsset) + : Value(InValue) + , GraphNode(InNode) + , OwningFlowAsset(InOwningFlowAsset) + , Parent(InParent) + , bIsSubGraphNode(bInIsSubGraphNode) { } @@ -68,50 +116,49 @@ TSharedRef FFindInFlowResult::CreateIcon() const .ColorAndOpacity(IconColor); } -FReply FFindInFlowResult::OnClick(TWeakPtr FlowAssetEditorPtr, TSharedPtr Root) +FReply FFindInFlowResult::OnClick(TWeakPtr FlowAssetEditorPtr) { - if (FlowAssetEditorPtr.IsValid() && GraphNode.IsValid()) + if (GraphNode.IsValid()) { - if (Parent.IsValid() && !bIsSubGraphNode) - { - FlowAssetEditorPtr.Pin()->JumpToNode(GraphNode.Get()); - } - else + if (UEdGraph* Graph = GraphNode->GetGraph()) { - FlowAssetEditorPtr.Pin()->JumpToNode(Parent.Pin()->GraphNode.Get()); + if (UFlowAsset* Asset = Cast(Graph->GetOuter())) + { + GEditor->GetEditorSubsystem()->OpenEditorForAsset(Asset); + if (TSharedPtr Editor = FFlowGraphUtils::GetFlowAssetEditor(Graph)) + { + Editor->JumpToNode(GraphNode.Get()); + } + } } } - + else if (OwningFlowAsset.IsValid()) + { + GEditor->GetEditorSubsystem()->OpenEditorForAsset(OwningFlowAsset.Get()); + } return FReply::Handled(); } -FReply FFindInFlowResult::OnDoubleClick(TSharedPtr Root) const +FReply FFindInFlowResult::OnDoubleClick() const { - if (!Parent.IsValid() || !bIsSubGraphNode) + if (bIsSubGraphNode && Parent.IsValid()) { - return FReply::Handled(); - } - const UFlowGraphNode* ParentGraphNode = Cast(Parent.Pin()->GraphNode); - if (!ParentGraphNode || !ParentGraphNode->GetFlowNodeBase()) - { - return FReply::Handled(); - } - - if (UFlowNode* FlowNode = Cast(ParentGraphNode->GetFlowNodeBase())) - { - if (UObject* AssetToEdit = FlowNode->GetAssetToEdit()) + if (const UFlowGraphNode* ParentNode = Cast(Parent.Pin()->GraphNode.Get())) { - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - if (AssetEditorSubsystem->OpenEditorForAsset(AssetToEdit)) + if (UFlowNode_SubGraph* SubGraph = Cast(ParentNode->GetFlowNodeBase())) { - if (const TSharedPtr FlowAssetEditor = FFlowGraphUtils::GetFlowAssetEditor(GraphNode->GetGraph())) + if (UObject* Target = SubGraph->GetAssetToEdit()) { - FlowAssetEditor->JumpToNode(GraphNode.Get()); + GEditor->GetEditorSubsystem()->OpenEditorForAsset(Target); + if (TSharedPtr Editor = FFlowGraphUtils::GetFlowAssetEditor(GraphNode->GetGraph())) + { + Editor->JumpToNode(GraphNode.Get()); + } } } } } - + return FReply::Handled(); } @@ -127,304 +174,789 @@ FString FFindInFlowResult::GetDescriptionText() const FString FFindInFlowResult::GetCommentText() const { - if (GraphNode.IsValid()) - { - return GraphNode->NodeComment; - } - - return FString(); + return GraphNode.IsValid() ? GraphNode->NodeComment : FString(); } FString FFindInFlowResult::GetNodeTypeText() const { - if (GraphNode.IsValid()) + if (!GraphNode.IsValid()) { - FString NodeClassName; - const UFlowGraphNode* FlowGraphNode = Cast(GraphNode.Get()); - if (FlowGraphNode && FlowGraphNode->GetFlowNodeBase()) - { - NodeClassName = FlowGraphNode->GetFlowNodeBase()->GetClass()->GetName(); - } - else - { - NodeClassName = GraphNode->GetClass()->GetName(); - } - const int32 Pos = NodeClassName.Find("_"); - if (Pos == INDEX_NONE) - { - return NodeClassName; - } - else + return FString(); + } + + if (const UFlowGraphNode* FlowGraphNode = Cast(GraphNode.Get())) + { + if (UFlowNodeBase* Base = FlowGraphNode->GetFlowNodeBase()) { - return NodeClassName.RightChop(Pos + 1); + return Base->GetClass()->GetDisplayNameText().ToString(); } } - return FString(); + return GraphNode->GetClass()->GetDisplayNameText().ToString(); } FText FFindInFlowResult::GetToolTipText() const { - FString ToolTipStr = TEXT("Click to focus on nodes."); - if (bIsSubGraphNode) + FString Tip = GetNodeTypeText() + TEXT("\n") + GetDescriptionText(); + + if (!GetCommentText().IsEmpty()) { - ToolTipStr += TEXT("\nDouble click to focus on subgraph nodes"); + Tip += TEXT("\n") + GetCommentText(); } - return FText::FromString(ToolTipStr); + + if (!MatchedPropertySnippet.IsEmpty()) + { + Tip += TEXT("\n\nMatched: ") + MatchedPropertySnippet; + } + + return FText::FromString(Tip); +} + +FText FFindInFlowResult::GetMatchedSnippet() const +{ + return FText::FromString(MatchedPropertySnippet); +} + +FText FFindInFlowResult::GetMatchedCategoriesText() const +{ + if (MatchedFlags == EFlowSearchFlags::None) + { + return FText::GetEmpty(); + } + + TArray DisplayNames; + + for (EFlowSearchFlags Flag : MakeFlagsRange(EFlowSearchFlags::All)) + { + if (EnumHasAnyFlags(MatchedFlags, Flag)) + { + FText DisplayName = UEnum::GetDisplayValueAsText(Flag); + if (!DisplayName.IsEmpty()) + { + DisplayNames.Add(DisplayName); + } + } + } + + if (DisplayNames.Num() == 0) + { + return FText::GetEmpty(); + } + + return FText::Join(FText::FromString(TEXT(", ")), DisplayNames); } ////////////////////////////////////////////////////////////////////////// // SFindInFlow -void SFindInFlow::Construct( const FArguments& InArgs, TSharedPtr InFlowAssetEditor) +void SFindInFlow::Construct(const FArguments& InArgs, TSharedPtr InFlowAssetEditor) { FlowAssetEditorPtr = InFlowAssetEditor; + SearchResults.Setup(); + + // Load INI settings + const UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get(); + if (ensure(Settings)) + { + MaxSearchDepth = Settings->DefaultMaxSearchDepth; + SearchFlags = static_cast(Settings->DefaultSearchFlags); + } - this->ChildSlot + // Populate scope options + FLOW_ASSERT_ENUM_MAX(EFlowSearchScope, 3); + for (EFlowSearchScope Scope : TEnumRange()) + { + if (FlowEnum::IsValidEnumValue(Scope)) + { + ScopeOptionList.Add(MakeShareable(new EFlowSearchScope(Scope))); + } + } + SelectedScopeOption = ScopeOptionList[0]; + + SAssignNew(SearchTextField, SSearchBox) + .OnTextCommitted(this, &SFindInFlow::OnSearchTextCommitted); + + SAssignNew(SearchButton, SButton) + .Text(LOCTEXT("SearchButton", "Search")) + .OnClicked(this, &SFindInFlow::OnSearchButtonClicked); + + SAssignNew(MaxDepthSpinBox, SSpinBox) + .MinValue(0) + .MaxValue(10) + .Value(MaxSearchDepth) + .OnValueChanged(this, &SFindInFlow::OnMaxDepthChanged) + .ToolTipText(LOCTEXT("MaxDepthTooltip", "Maximum recursion depth when searching inside objects")); + + SAssignNew(TreeView, STreeViewType) + .TreeItemsSource(&SearchResults.ItemsFound) + .OnGenerateRow(this, &SFindInFlow::OnGenerateRow) + .OnGetChildren(this, &SFindInFlow::OnGetChildren) + .OnSelectionChanged(this, &SFindInFlow::OnTreeSelectionChanged) + .OnMouseButtonDoubleClick(this, &SFindInFlow::OnTreeSelectionDoubleClicked); + + ChildSlot [ SNew(SVerticalBox) - +SVerticalBox::Slot() - .AutoHeight() - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .FillWidth(1) - [ - SAssignNew(SearchTextField, SSearchBox) - .HintText(LOCTEXT("FlowEditorSearchHint", "Enter text to find nodes...")) - .OnTextChanged(this, &SFindInFlow::OnSearchTextChanged) - .OnTextCommitted(this, &SFindInFlow::OnSearchTextCommitted) - ] - +SHorizontalBox::Slot() - .Padding(10,0,5,0) - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(LOCTEXT("FlowEditorSubGraphSearchText", "Find In SubGraph ")) - ] - +SHorizontalBox::Slot() - .AutoWidth() + + SVerticalBox::Slot() + .AutoHeight() [ - SNew(SCheckBox) - .OnCheckStateChanged(this, &SFindInFlow::OnFindInSubGraphStateChanged) - .ToolTipText(LOCTEXT("FlowEditorSubGraphSearchHint", "Checkin means search also in sub graph.")) + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + SearchTextField.ToSharedRef() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SearchButton.ToSharedRef() + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(LOCTEXT("FiltersLabel", "Filters:")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(SButton) + .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") + .ToolTipText(LOCTEXT("EditFiltersTooltip", "Edit search filters")) + .OnClicked_Lambda([this]() + { + FFindInFlowApplyDelegate OnSaveAsDefault = FFindInFlowApplyDelegate::CreateLambda([this](EFlowSearchFlags Flags) + { + if (UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get()) + { + Settings->DefaultSearchFlags = static_cast(Flags); + Settings->SaveConfig(); + } + }); + + TSharedRef FilterPopup = SNew(SFindInFlowFilterPopup) + .OnApply(FFindInFlowApplyDelegate::CreateLambda([this](EFlowSearchFlags NewSearchFlags) + { + SearchFlags = NewSearchFlags; + + InitiateSearch(); + })) + .OnSaveAsDefault(OnSaveAsDefault) + .InitialFlags(SearchFlags); + + FSlateApplication::Get().PushMenu( + AsShared(), + FWidgetPath(), + FilterPopup, + FSlateApplication::Get().GetCursorPos(), + FPopupTransitionEffect::ContextMenu); + + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text_Lambda([this]() + { + int32 ActiveCount = FMath::CountBits(static_cast(SearchFlags)); + return FText::Format(LOCTEXT("ActiveFilters", "{0} Active"), FText::AsNumber(ActiveCount)); + }) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(SComboBox>) + .OptionsSource(&ScopeOptionList) + .OnGenerateWidget(this, &SFindInFlow::GenerateScopeWidget) + .OnSelectionChanged(this, &SFindInFlow::OnScopeChanged) + [ + SNew(STextBlock).Text(this, &SFindInFlow::GetCurrentScopeText) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(LOCTEXT("MaxDepthLabel", "Max Depth:")) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + MaxDepthSpinBox.ToSharedRef() + ] ] - ] - +SVerticalBox::Slot() - .FillHeight(1.0f) - .Padding(0.f, 4.f, 0.f, 0.f) - [ - SNew(SBorder) - .BorderImage(FAppStyle::GetBrush("Menu.Background")) + + SVerticalBox::Slot() + .FillHeight(1.0f) [ - SAssignNew(TreeView, STreeViewType) - .TreeItemsSource(&ItemsFound) - .OnGenerateRow(this, &SFindInFlow::OnGenerateRow) - .OnGetChildren(this, &SFindInFlow::OnGetChildren) - .OnSelectionChanged(this, &SFindInFlow::OnTreeSelectionChanged) - .OnMouseButtonDoubleClick(this, &SFindInFlow::OnTreeSelectionDoubleClicked) - .SelectionMode(ESelectionMode::Multi) + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [ + TreeView.ToSharedRef() + ] ] - ] ]; } void SFindInFlow::FocusForUse() const { - // 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); + if (SearchTextField.IsValid()) + { + FSlateApplication::Get().SetKeyboardFocus(SearchTextField.ToSharedRef()); + SearchTextField->SelectAllText(); + } } void SFindInFlow::OnSearchTextChanged(const FText& Text) { SearchValue = Text.ToString(); - +} + +void SFindInFlow::OnSearchTextCommitted(const FText& Text, ETextCommit::Type) +{ + SearchValue = Text.ToString(); + + InitiateSearch(); +} + +FReply SFindInFlow::OnSearchButtonClicked() +{ InitiateSearch(); + + return FReply::Handled(); +} + +void SFindInFlow::OnScopeChanged(TSharedPtr NewSelection, ESelectInfo::Type) +{ + SelectedScopeOption = NewSelection; + SearchScope = *NewSelection; } -void SFindInFlow::OnSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType) +void SFindInFlow::OnMaxDepthChanged(int32 NewDepth) { - OnSearchTextChanged(Text); + MaxSearchDepth = NewDepth; + + // Save to INI + if (UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get()) + { + Settings->DefaultMaxSearchDepth = NewDepth; + Settings->SaveConfig(); + } +} + +TSharedRef SFindInFlow::GenerateScopeWidget(TSharedPtr Item) const +{ + return SNew(STextBlock) + .Text(UEnum::GetDisplayValueAsText(*Item.Get())); +} + +FText SFindInFlow::GetCurrentScopeText() const +{ + return UEnum::GetDisplayValueAsText(*SelectedScopeOption.Get()); } void SFindInFlow::InitiateSearch() { + FFlowEditorModule* FlowEditorModule = &FModuleManager::LoadModuleChecked("FlowEditor"); + if (ensure(FlowEditorModule)) + { + FlowEditorModule->RegisterForAssetChanges(); + } + + SearchResults.Reset(); + + HighlightText = FText::FromString(SearchValue); + TreeView->RequestTreeRefresh(); + + if (SearchValue.IsEmpty()) + { + return; + } + TArray Tokens; SearchValue.ParseIntoArray(Tokens, TEXT(" "), true); + for (FString& Token : Tokens) + { + Token = Token.ToUpper(); + } + + TSharedPtr Editor = FlowAssetEditorPtr.Pin(); + if (!Editor.IsValid()) + { + return; + } - for (auto It(ItemsFound.CreateIterator()); It; ++It) + UFlowAsset* CurrentAsset = Editor->GetFlowAsset(); + if (!CurrentAsset || !CurrentAsset->GetGraph()) { - TreeView->SetItemExpansion(*It, false); + return; } - ItemsFound.Empty(); - if (Tokens.Num() > 0) + + const TSubclassOf CurrentAssetClass = CurrentAsset->GetClass(); + + constexpr int32 Depth = 0; + + switch (SearchScope) { - HighlightText = FText::FromString(SearchValue); - MatchTokens(Tokens); + case EFlowSearchScope::ThisAssetOnly: + { + FSearchResult AssetRoot = MakeShareable(new FFindInFlowResult(CurrentAsset->GetName(), CurrentAsset)); + ProcessAsset(CurrentAsset, AssetRoot, Tokens, Depth); + + if (AssetRoot->Children.Num() > 0) + { + SearchResults.ItemsFound.Add(AssetRoot); + + // Auto-expand the current asset's results + TreeView->SetItemExpansion(AssetRoot, true); + } + } + break; + + case EFlowSearchScope::AllOfThisType: + case EFlowSearchScope::AllFlowAssets: + { + FAssetRegistryModule& Registry = FModuleManager::LoadModuleChecked("AssetRegistry"); + TArray Assets; + FARFilter Filter; + Filter.bRecursiveClasses = true; + + if (SearchScope == EFlowSearchScope::AllFlowAssets) + { + Filter.ClassPaths.Add(FTopLevelAssetPath(UFlowAsset::StaticClass()->GetClassPathName())); + } + else + { + Filter.ClassPaths.Add(FTopLevelAssetPath(CurrentAsset->GetClass()->GetClassPathName())); + } + + Registry.Get().GetAssets(Filter, Assets); + + FScopedSlowTask Task(Assets.Num(), LOCTEXT("SearchingAssets", "Searching Flow Assets...")); + Task.MakeDialog(); + + int32 CurrentAssetIndex = 0; + + for (const FAssetData& Data : Assets) + { + UFlowAsset* Asset = Cast(Data.GetAsset()); + if (!IsValid(Asset)) + { + continue; + } + + CurrentAssetIndex++; + + Task.EnterProgressFrame(1, FText::Format(LOCTEXT("SearchingAsset", "Searching {0}/{1}: {2}..."), CurrentAssetIndex, Assets.Num(), FText::FromString(Asset->GetName()))); + + FSearchResult AssetRoot = MakeShareable(new FFindInFlowResult(Asset->GetName(), Asset)); + ProcessAsset(Asset, AssetRoot, Tokens, Depth); + + if (AssetRoot->Children.Num() > 0) + { + SearchResults.ItemsFound.Add(AssetRoot); + + // Auto-expand only the current asset + if (Asset == CurrentAsset) + { + TreeView->SetItemExpansion(AssetRoot, true); + } + } + } + } + break; + + default: + checkNoEntry(); + break; } - // Insert a fake result to inform user if none found - if (ItemsFound.Num() == 0) + // Add "No results" placeholder if nothing found + if (SearchResults.ItemsFound.IsEmpty()) { - ItemsFound.Add(MakeShared(LOCTEXT("FlowEditorSearchNoResults", "No Results found").ToString())); + FSearchResult NoResults = MakeShareable(new FFindInFlowResult(TEXT("No results found"))); + SearchResults.ItemsFound.Add(NoResults); } TreeView->RequestTreeRefresh(); +} + +bool SFindInFlow::ProcessAsset(UFlowAsset* Asset, FSearchResult ParentResult, const TArray& Tokens, int32 Depth) +{ + if (!Asset || !Asset->GetGraph() || Depth >= MaxSearchDepth || SearchResults.VisitedAssets.Contains(Asset)) + { + return false; + } + + SearchResults.VisitedAssets.Add(Asset); + + bool bAnyMatches = false; - for (auto It(ItemsFound.CreateIterator()); It; ++It) + for (UEdGraphNode* EdNode : Asset->GetGraph()->Nodes) { - TreeView->SetItemExpansion(*It, true); + const TMap>* CategoryStrings = BuildCategoryStrings(EdNode, Depth); + + if (!CategoryStrings) + { + continue; + } + + EFlowSearchFlags NodeMatchedFlags = EFlowSearchFlags::None; + + for (const TPair>& Pair : *CategoryStrings) + { + const TSet& StringSet = Pair.Value; + if (EnumHasAnyFlags(SearchFlags, Pair.Key) && StringSetMatchesSearchTokens(Tokens, StringSet)) + { + EnumAddFlags(NodeMatchedFlags, Pair.Key); + } + } + + if (NodeMatchedFlags != EFlowSearchFlags::None) + { + FString Title = EdNode->GetNodeTitle(ENodeTitleType::ListView).ToString(); + if (Title.IsEmpty()) + { + Title = EdNode->GetClass()->GetName(); + } + + FSearchResult Result = MakeShareable(new FFindInFlowResult(Title, ParentResult, EdNode, Depth > 0, Asset)); + Result->MatchedFlags = NodeMatchedFlags; + ParentResult->Children.Add(Result); + + bAnyMatches = true; + } + + bAnyMatches |= RecurseIntoSubgraphsIfEnabled(EdNode, ParentResult, Tokens, Depth); } + + return bAnyMatches; } -void SFindInFlow::MatchTokens(const TArray& Tokens) +bool SFindInFlow::RecurseIntoSubgraphsIfEnabled(UEdGraphNode* EdNode, FSearchResult ParentResult, const TArray& Tokens, int32 Depth) { - RootSearchResult.Reset(); - - const UEdGraph* Graph = nullptr; - const TSharedPtr FocusedGraphEditor = FlowAssetEditorPtr.Pin()->GetFlowGraph(); - if (FocusedGraphEditor.IsValid()) + if (!EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Subgraphs)) + { + return false; + } + + UFlowGraphNode* FlowGraphNode = Cast(EdNode); + if (!FlowGraphNode || !FlowGraphNode->GetFlowNodeBase()) + { + return false; + } + + UFlowNode_SubGraph* SubGraph = Cast(FlowGraphNode->GetFlowNodeBase()); + if (!SubGraph) + { + return false; + } + + UFlowAsset* SubAsset = Cast(SubGraph->GetAssetToEdit()); + if (!SubAsset) + { + return false; + } + + const FString SubgraphStr = + SearchResults.VisitedAssets.Contains(SubAsset) ? + TEXT(" (repeat subgraph)") : + TEXT(" (Subgraph)"); + + const FString SubTitle = SubAsset->GetName() + SubgraphStr; + FSearchResult SubResult = MakeShareable(new FFindInFlowResult(SubTitle, ParentResult, EdNode, true, SubAsset)); + + // Subgraphs don't count against depth + if (ProcessAsset(SubAsset, SubResult, Tokens, Depth)) + { + ParentResult->Children.Add(SubResult); + + return true; + } + + return false; +} + +const TMap>* SFindInFlow::BuildCategoryStrings(UEdGraphNode* EdNode, int32 Depth) const +{ + if (!IsValid(EdNode)) + { + return nullptr; + } + + // Check cache first + if (const TMap>* Cached = FFindInFlowCache::CategoryStringCache.Find(EdNode)) + { + return Cached; + } + + TMap> NewResultMap; + + UpdateSearchFlagToStringMapForEdGraphNode(*EdNode, NewResultMap, Depth); + + UFlowGraphNode* FlowGraphNode = Cast(EdNode); + if (IsValid(FlowGraphNode)) + { + UFlowNodeBase* FlowNodeBase = FlowGraphNode->GetFlowNodeBase(); + if (IsValid(FlowNodeBase)) + { + UpdateSearchFlagToStringMapForFlowNodeBase(*FlowNodeBase, NewResultMap, Depth); + } + } + + // Now add the new map to the search cache + const TMap>* AddedResultMap = &FFindInFlowCache::CategoryStringCache.Add(EdNode, NewResultMap); + return AddedResultMap; +} + +void SFindInFlow::UpdateSearchFlagToStringMapForEdGraphNode(const UEdGraphNode& EdGraphNode, TMap>& SearchFlagToStringMap, int32 Depth) const +{ + // Comments + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Comments)) + { + TSet& CommentsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Comments); + CommentsSet.Add(EdGraphNode.NodeComment); + } +} + +void SFindInFlow::UpdateSearchFlagToStringMapForFlowNodeBase(const UFlowNodeBase& FlowNodeBase, TMap>& SearchFlagToStringMap, int32 Depth) const +{ + // Node Titles + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Titles)) + { + TSet& TitlesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Titles); + TitlesSet.Add(FlowNodeBase.GetNodeTitle().ToString()); + } + + // Tooltips + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Tooltips)) + { + TSet& TooltipsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Tooltips); + TooltipsSet.Add(FlowNodeBase.GetNodeToolTip().ToString()); + } + + // Classes + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Classes)) + { + TSet& ClassesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Classes); + + const FString DisplayName = FlowNodeBase.GetClass()->GetDisplayNameText().ToString(); + ClassesSet.Add(DisplayName); + + const FString NativeName = FlowNodeBase.GetClass()->GetName(); + ClassesSet.Add(NativeName); + } + + // Descriptions + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Descriptions)) + { + TSet& DescriptionsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Descriptions); + + DescriptionsSet.Add(FlowNodeBase.GetNodeDescription()); + } + + // Config Text + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::ConfigText)) + { + TSet& ConfigSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::ConfigText); + ConfigSet.Add(FlowNodeBase.GetNodeConfigText().ToString()); + } + + // Property-based scouring + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::PropertiesFlags)) + { + AppendPropertyValues(&FlowNodeBase, FlowNodeBase.GetClass(), &FlowNodeBase, SearchFlagToStringMap, Depth); + } + + // AddOns + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::AddOns)) + { + TSet& AddOnsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::AddOns); + + FlowNodeBase.ForEachAddOnConst([AddOnsSet, this, &SearchFlagToStringMap, &Depth](const UFlowNodeAddOn& AddOn) + { + // No depth penalty for AddOns + UpdateSearchFlagToStringMapForFlowNodeBase(AddOn, SearchFlagToStringMap, Depth); + + return EFlowForEachAddOnFunctionReturnValue::Continue; + }); + } +} + +void SFindInFlow::AppendPropertyValues(const void* Container, const UStruct* Struct, const UObject* ParentObject, TMap>& SearchFlagToStringMap, int32 Depth) const +{ + int32 MaxDepth = 1; + if (const UFlowGraphEditorSettings* Settings = UFlowGraphEditorSettings::Get()) { - Graph = FocusedGraphEditor->GetCurrentGraph(); + MaxDepth = Settings->DefaultMaxSearchDepth; } - if (Graph == nullptr) + if (!Container || !Struct || !ParentObject || Depth >= MaxDepth) { return; } - - RootSearchResult = MakeShared(FString("FlowEditorRoot")); - for (auto It(Graph->Nodes.CreateConstIterator()); It; ++It) + for (TFieldIterator It(Struct, EFieldIteratorFlags::IncludeSuper); It; ++It) { - UEdGraphNode* Node = *It; - - const FString NodeName = Node->GetNodeTitle(ENodeTitleType::ListView).ToString(); - FSearchResult NodeResult(new FFindInFlowResult(NodeName, RootSearchResult, Node)); - FString NodeSearchString = NodeName + Node->GetClass()->GetName() + Node->NodeComment; + FProperty* Prop = *It; + if (!Prop->HasAnyPropertyFlags(CPF_Edit | CPF_SimpleDisplay | CPF_AdvancedDisplay | CPF_BlueprintVisible | CPF_Config)) + { + continue; + } - if (const UFlowGraphNode* FlowGraphNode = Cast(Node)) + const void* ValuePtr = Prop->ContainerPtrToValuePtr(Container); + + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::PropertyNames)) { - FString NodeDescription = FlowGraphNode->GetNodeDescription(); - NodeSearchString += NodeDescription; - - UFlowNode_SubGraph* SubGraphNode = Cast(FlowGraphNode->GetFlowNodeBase()); - if (bFindInSubGraph && SubGraphNode) + TSet& PropertyNamesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::PropertyNames); + + const FString DisplayName = Prop->GetMetaData(TEXT("DisplayName")); + + if (!DisplayName.IsEmpty()) { - if (const UFlowAsset* FlowAsset = Cast(SubGraphNode->GetAssetToEdit()); FlowAsset && FlowAsset->GetGraph()) - { - for (auto ChildIt(FlowAsset->GetGraph()->Nodes.CreateConstIterator()); ChildIt; ++ChildIt) - { - MatchTokensInChild(Tokens, *ChildIt, NodeResult); - } - } + PropertyNamesSet.Add(DisplayName); } + + PropertyNamesSet.Add(Prop->GetName()); + } + + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::PropertyValues)) + { + TSet& PropertyValuesSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::PropertyValues); + + FString ValueStr; + UObject* MutableParentObject = const_cast(ParentObject); + Prop->ExportText_InContainer(0, ValueStr, Container, nullptr, MutableParentObject, PPF_None); + ValueStr = ValueStr.Replace(TEXT("\""), TEXT("")).TrimStartAndEnd(); + + PropertyValuesSet.Add(ValueStr); } - NodeSearchString = NodeSearchString.Replace(TEXT(" "), TEXT("")); - const bool bNodeMatchesSearch = StringMatchesSearchTokens(Tokens, NodeSearchString); - - if ((NodeResult->Children.Num() > 0) || bNodeMatchesSearch) + if (EnumHasAnyFlags(SearchFlags, EFlowSearchFlags::Tooltips)) { - ItemsFound.Add(NodeResult); + TSet& TooltipsSet = SearchFlagToStringMap.FindOrAdd(EFlowSearchFlags::Tooltips); + TooltipsSet.Add(Prop->GetMetaData(TEXT("ToolTip"))); + } + + if (FStructProperty* StructProp = CastField(Prop)) + { + // Recurse into structs (no depth penalty) + AppendPropertyValues(ValuePtr, StructProp->Struct, ParentObject, SearchFlagToStringMap, Depth); + } + else if (FObjectProperty* ObjProp = CastField(Prop)) + { + // Recurse into inline objects (incurs a depth penalty) + UObject* Obj = ObjProp->GetObjectPropertyValue(ValuePtr); + if (IsValid(Obj) && !Obj->HasAnyFlags(RF_ClassDefaultObject)) + { + AppendPropertyValues(Obj, Obj->GetClass(), Obj, SearchFlagToStringMap, Depth + 1); + } } } } -void SFindInFlow::MatchTokensInChild(const TArray& Tokens, UEdGraphNode* Child, FSearchResult ParentNode) +bool SFindInFlow::StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString) { - if (Child == nullptr) + int32 MatchedTokenCount = 0; + const int32 TotalTokenCount = Tokens.Num(); + + // Must match all tokens + for (const FString& Token : Tokens) { - return; + if (ComparisonString.Contains(Token)) + { + ++MatchedTokenCount; + } + else + { + break; + } } - const FString ChildName = Child->GetNodeTitle(ENodeTitleType::ListView).ToString(); - FString ChildSearchString = ChildName + Child->GetClass()->GetName() + Child->NodeComment; - if (const UFlowGraphNode* FlowGraphNode = Cast(Child)) + if (MatchedTokenCount == TotalTokenCount) { - FString NodeDescription = FlowGraphNode->GetNodeDescription(); - ChildSearchString += NodeDescription; + return true; } - ChildSearchString = ChildSearchString.Replace(TEXT(" "), TEXT("")); - if (StringMatchesSearchTokens(Tokens, ChildSearchString)) + else { - const FSearchResult DecoratorResult(new FFindInFlowResult(ChildName, ParentNode, Child, true)); - ParentNode->Children.Add(DecoratorResult); + return false; + } +} + +bool SFindInFlow::StringSetMatchesSearchTokens(const TArray& Tokens, const TSet& StringSet) +{ + for (const FString& StringFromSet : StringSet) + { + if (StringMatchesSearchTokens(Tokens, StringFromSet)) + { + return true; + } } + + return false; } -TSharedRef SFindInFlow::OnGenerateRow( FSearchResult InItem, const TSharedRef& OwnerTable ) +TSharedRef SFindInFlow::OnGenerateRow(FSearchResult InItem, const TSharedRef& OwnerTable) { - return SNew(STableRow< TSharedPtr >, OwnerTable) + return SNew(STableRow, OwnerTable) .ToolTip(SNew(SToolTip).Text(InItem->GetToolTipText())) [ SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SBox) - .MinDesiredWidth(300) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 0) [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .AutoWidth() - [ - InItem->CreateIcon() - ] - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - .AutoWidth() - .Padding(2, 0) - [ - SNew(STextBlock) + InItem->CreateIcon() + ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) .Text(FText::FromString(InItem->Value)) .HighlightText(HighlightText) - ] ] - ] - +SHorizontalBox::Slot() - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(InItem->GetDescriptionText())) - .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) - ] + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(FText::FromString(InItem->GetNodeTypeText())) + .ColorAndOpacity(FSlateColor(FLinearColor(0.6f, 0.8f, 1.0f))) + ] + + SHorizontalBox::Slot() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + .Padding(4, 0) + [ + SNew(STextBlock) + .Text(InItem->GetMatchedCategoriesText()) + .ColorAndOpacity(FSlateColor(FLinearColor(0.8f, 0.8f, 0.8f))) + ] ]; } -void SFindInFlow::OnGetChildren(FSearchResult InItem, TArray< FSearchResult >& OutChildren) +void SFindInFlow::OnGetChildren(FSearchResult InItem, TArray& OutChildren) { - OutChildren += InItem->Children; + OutChildren = InItem->Children; } -void SFindInFlow::OnTreeSelectionChanged(FSearchResult Item , ESelectInfo::Type) +void SFindInFlow::OnTreeSelectionChanged(FSearchResult Item, ESelectInfo::Type) { if (Item.IsValid()) { - Item->OnClick(FlowAssetEditorPtr, RootSearchResult); + Item->OnClick(FlowAssetEditorPtr); } } @@ -432,33 +964,8 @@ void SFindInFlow::OnTreeSelectionDoubleClicked(FSearchResult Item) { if (Item.IsValid()) { - Item->OnDoubleClick(RootSearchResult); + Item->OnDoubleClick(); } } -void SFindInFlow::OnFindInSubGraphStateChanged(ECheckBoxState CheckBoxState) -{ - bFindInSubGraph = CheckBoxState == ECheckBoxState::Checked; - InitiateSearch(); -} - -bool SFindInFlow::StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString) -{ - bool bFoundAllTokens = true; - - //search the entry for each token, it must have all of them to pass - for (auto TokItr(Tokens.CreateConstIterator()); TokItr; ++TokItr) - { - const FString& Token = *TokItr; - if (!ComparisonString.Contains(Token)) - { - bFoundAllTokens = false; - break; - } - } - return bFoundAllTokens; -} - -///////////////////////////////////////////////////// - #undef LOCTEXT_NAMESPACE diff --git a/Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp b/Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp new file mode 100644 index 000000000..7735a2d90 --- /dev/null +++ b/Source/FlowEditor/Private/Find/SFindInFlowFilterPopup.cpp @@ -0,0 +1,145 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "Find/SFindInFlowFilterPopup.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Framework/Application/SlateApplication.h" + +#define LOCTEXT_NAMESPACE "FindInFlow" + +void SFindInFlowFilterPopup::Construct(const FArguments& InArgs) +{ + OnApplyDelegate = InArgs._OnApply; + OnSaveAsDefaultDelegate = InArgs._OnSaveAsDefault; + ProposedFlags = InArgs._InitialFlags; + + // Build the checkbox container with slots added during construction + SAssignNew(CheckBoxContainer, SVerticalBox); + + for (EFlowSearchFlags Flag : MakeFlagsRange(EFlowSearchFlags::All)) + { + CheckBoxContainer->AddSlot() + .AutoHeight() + [ + SNew(SCheckBox) + .IsChecked(this, &SFindInFlowFilterPopup::GetCheckState, Flag) + .OnCheckStateChanged_Lambda([this, Flag](ECheckBoxState NewState) + { + if (NewState == ECheckBoxState::Checked) + { + EnumAddFlags(ProposedFlags, Flag); + } + else + { + EnumRemoveFlags(ProposedFlags, Flag); + } + }) + [ + SNew(STextBlock) + .Text(UEnum::GetDisplayValueAsText(Flag)) + ] + ]; + } + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(10) + [ + SNew(STextBlock) + .Text(LOCTEXT("FilterPopupTitle", "Select Search Filters:")) + .Font(FAppStyle::GetFontStyle("NormalFontBold")) + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(10, 5) + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + CheckBoxContainer.ToSharedRef() + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(10) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("ToggleAllFilters", "Toggle All")) + .OnClicked(this, &SFindInFlowFilterPopup::OnToggleAllClicked) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("SaveAsDefaultFilters", "Save as Default")) + .OnClicked(this, &SFindInFlowFilterPopup::OnSaveAsDefaultClicked) + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + .Padding(10) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("CancelFilters", "Cancel")) + .OnClicked(this, &SFindInFlowFilterPopup::OnCancelClicked) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("ApplyFilters", "Apply")) + .OnClicked(this, &SFindInFlowFilterPopup::OnApplyClicked) + ] + ] + ]; +} + +FReply SFindInFlowFilterPopup::OnApplyClicked() +{ + OnApplyDelegate.ExecuteIfBound(ProposedFlags); + FSlateApplication::Get().DismissAllMenus(); + return FReply::Handled(); +} + +FReply SFindInFlowFilterPopup::OnCancelClicked() +{ + FSlateApplication::Get().DismissAllMenus(); + return FReply::Handled(); +} + +FReply SFindInFlowFilterPopup::OnToggleAllClicked() +{ + if (ProposedFlags != EFlowSearchFlags::None) + { + ProposedFlags = EFlowSearchFlags::None; + } + else + { + ProposedFlags = EFlowSearchFlags::All; + } + + CheckBoxContainer->Invalidate(EInvalidateWidgetReason::Layout); + + return FReply::Handled(); +} + +FReply SFindInFlowFilterPopup::OnSaveAsDefaultClicked() +{ + OnSaveAsDefaultDelegate.ExecuteIfBound(ProposedFlags); + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Private/FlowEditorModule.cpp b/Source/FlowEditor/Private/FlowEditorModule.cpp index 282fab0d2..74f5e5536 100644 --- a/Source/FlowEditor/Private/FlowEditorModule.cpp +++ b/Source/FlowEditor/Private/FlowEditorModule.cpp @@ -35,6 +35,7 @@ #include "FlowAsset.h" #include "AddOns/FlowNodeAddOn.h" #include "Asset/FlowAssetParamsTypes.h" +#include "Find/FindInFlow.h" #include "Nodes/Actor/FlowNode_ComponentObserver.h" #include "Nodes/Actor/FlowNode_PlayLevelSequence.h" #include "Nodes/Graph/FlowNode_CustomInput.h" @@ -43,6 +44,7 @@ #include "Types/FlowNamedDataPinProperty.h" #include "AssetToolsModule.h" +#include "AssetRegistry/AssetRegistryModule.h" #include "EdGraphUtilities.h" #include "IAssetSearchModule.h" #include "Framework/MultiBox/MultiBoxBuilder.h" @@ -100,6 +102,20 @@ void FFlowEditorModule::StartupModule() ModulesChangedHandle = FModuleManager::Get().OnModulesChanged().AddRaw(this, &FFlowEditorModule::ModulesChangesCallback); } +void FFlowEditorModule::RegisterForAssetChanges() +{ + if (!bIsRegisteredForAssetChanges) + { + // Register asset change detection for search cache invalidation + FAssetRegistryModule& AssetRegistry = FModuleManager::LoadModuleChecked("AssetRegistry"); + AssetRegistry.Get().OnAssetUpdated().AddRaw(this, &FFlowEditorModule::OnAssetUpdated); + AssetRegistry.Get().OnAssetRenamed().AddRaw(this, &FFlowEditorModule::OnAssetRenamed); + AssetRegistry.Get().OnAssetRemoved().AddRaw(this, &FFlowEditorModule::OnAssetUpdated); + + bIsRegisteredForAssetChanges = true; + } +} + void FFlowEditorModule::ShutdownModule() { MenuExtensibilityManager.Reset(); @@ -116,6 +132,18 @@ void FFlowEditorModule::ShutdownModule() SequencerModule.UnRegisterTrackEditor(FlowTrackCreateEditorHandle); FModuleManager::Get().OnModulesChanged().Remove(ModulesChangedHandle); + + if (bIsRegisteredForAssetChanges && FModuleManager::Get().IsModuleLoaded("AssetRegistry")) + { + // Unregister asset change detection + FAssetRegistryModule& AssetRegistry = FModuleManager::Get().GetModuleChecked("AssetRegistry"); + + AssetRegistry.Get().OnAssetUpdated().RemoveAll(this); + AssetRegistry.Get().OnAssetRenamed().RemoveAll(this); + AssetRegistry.Get().OnAssetRemoved().RemoveAll(this); + + bIsRegisteredForAssetChanges = false; + } } void FFlowEditorModule::TrySetFlowNodeDisplayStyleDefaults() const @@ -314,6 +342,19 @@ TSharedRef FFlowEditorModule::CreateFlowAssetEditor(const EToo return NewFlowAssetEditor; } +void FFlowEditorModule::OnAssetUpdated(const FAssetData& AssetData) +{ + if (UFlowAsset* FlowAsset = Cast(AssetData.GetAsset())) + { + FFindInFlowCache::OnFlowAssetChanged(*FlowAsset); + } +} + +void FFlowEditorModule::OnAssetRenamed(const FAssetData& AssetData, const FString& OldObjectPath) +{ + OnAssetUpdated(AssetData); +} + #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FFlowEditorModule, FlowEditor) \ No newline at end of file diff --git a/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp index b6d4cfea7..78c54ff23 100644 --- a/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Widgets/SFlowGraphNode.cpp @@ -416,7 +416,7 @@ FSlateColor SFlowGraphNode::GetConfigBoxBackgroundColor() const void SFlowGraphNode::CreateBelowPinControls(const TSharedPtr InnerVerticalBox) { - static const FMargin ConfigBoxPadding = FMargin(2.0f, 0.0f, 1.0f, 0.0); + static const FMargin ConfigBoxPadding = FMargin(2.0f, 0.0f, 1.0f, 0.0f); // Add a box to wrap around the Config Text area to make it a more visually distinct part of the node TSharedPtr BelowPinsBox; @@ -1224,4 +1224,4 @@ void SFlowGraphNode::SetOwner(const TSharedRef& OwnerPanel) } } -#undef LOCTEXT_NAMESPACE +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/FlowEditor/Public/Find/FindInFlow.h b/Source/FlowEditor/Public/Find/FindInFlow.h index 848bcff4d..9c8321416 100644 --- a/Source/FlowEditor/Public/Find/FindInFlow.h +++ b/Source/FlowEditor/Public/Find/FindInFlow.h @@ -20,32 +20,38 @@ #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" +#include "Widgets/Input/SSpinBox.h" #include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STreeView.h" +#include "Types/FlowEnumUtils.h" +#include "FindInFlowEnums.h" + class ITableRow; class SWidget; class UFlowGraphNode; class UEdGraphNode; +class UFlowAsset; +class UFlowNodeBase; /** Item that matched the search results */ class FFindInFlowResult { -public: +public: /** Create a root (or only text) result */ - FFindInFlowResult(const FString& InValue); - + FFindInFlowResult(const FString& InValue, UFlowAsset* InOwningFlowAsset = nullptr); + /** Create a flow node result */ - FFindInFlowResult(const FString& InValue, TSharedPtr& InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode = false); + FFindInFlowResult(const FString& InValue, TSharedPtr InParent, UEdGraphNode* InNode, bool bInIsSubGraphNode = false, UFlowAsset* InOwningFlowAsset = nullptr); /** Called when user clicks on the search item */ - FReply OnClick(TWeakPtr FlowAssetEditor, TSharedPtr Root); - + FReply OnClick(TWeakPtr FlowAssetEditorPtr); + /** Called when user double clicks on the search item */ - FReply OnDoubleClick(TSharedPtr Root) const; + FReply OnDoubleClick() const; /** Create an icon to represent the result */ - TSharedRef CreateIcon() const; + TSharedRef CreateIcon() const; /** Gets the description on flow node if any */ FString GetDescriptionText() const; @@ -59,15 +65,30 @@ class FFindInFlowResult /** Gets the node tool tip */ FText GetToolTipText() const; - /** Any children listed under this flow node (decorators and services) */ + /** Returns a snippet of the matched property/value for tooltip */ + FText GetMatchedSnippet() const; + + /** Human-readable list of categories this result matched in */ + FText GetMatchedCategoriesText() const; + + /** Any children listed under this flow node (decorators, services, addons, subnodes) */ TArray< TSharedPtr > Children; /** The string value for this result */ FString Value; + /** Stores a snippet of the matched property/value (e.g. "Damage:50") */ + FString MatchedPropertySnippet; + + /** Which search categories actually produced a hit for this item */ + EFlowSearchFlags MatchedFlags = EFlowSearchFlags::None; + /** The graph node that this search result refers to */ TWeakObjectPtr GraphNode; + /** The owning flow asset for this result */ + TWeakObjectPtr OwningFlowAsset; + /** Search result parent */ TWeakPtr Parent; @@ -75,11 +96,46 @@ class FFindInFlowResult bool bIsSubGraphNode = false; }; +struct FFindInFlowCache +{ + /** Removes all cached data for the changed flow asset */ + static void OnFlowAssetChanged(UFlowAsset& ChangedFlowAsset); + + /** Cache searchable strings per node (for repeat searches) */ + static TMap, TMap>> CategoryStringCache; +}; + +struct FFindInFlowAllResults +{ + typedef TSharedPtr FSearchResult; + + /** we need to keep a handle on the root result, because it won't show up in the tree */ + FSearchResult RootSearchResult; + + /** This buffer stores the currently displayed results */ + TArray ItemsFound; + + /** Visited assets to prevent cycles in subgraph recursion */ + TSet VisitedAssets; + + void Setup() + { + RootSearchResult = MakeShareable(new FFindInFlowResult(TEXT("Root"))); + } + + void Reset() + { + ItemsFound.Empty(); + RootSearchResult->Children.Empty(); + VisitedAssets.Empty(); + } +}; + /** Widget for searching for (Flow nodes) across focused FlowNodes */ class SFindInFlow : public SCompoundWidget { public: - SLATE_BEGIN_ARGS(SFindInFlow){} + SLATE_BEGIN_ARGS(SFindInFlow) {} SLATE_END_ARGS() void Construct(const FArguments& InArgs, TSharedPtr InFlowAssetEditor); @@ -87,7 +143,8 @@ class SFindInFlow : public SCompoundWidget /** Focuses this widget's search box */ void FocusForUse() const; -private: +protected: + typedef TSharedPtr FSearchResult; typedef STreeView STreeViewType; @@ -97,55 +154,84 @@ class SFindInFlow : public SCompoundWidget /** Called when user commits text */ void OnSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType); + /** Called when search button is clicked */ + FReply OnSearchButtonClicked(); + /** Get the children of a row */ void OnGetChildren(FSearchResult InItem, TArray& OutChildren); /** Called when user clicks on a new result */ void OnTreeSelectionChanged(FSearchResult Item, ESelectInfo::Type SelectInfo); - + /* Called when user double clicks on a new result */ - void OnTreeSelectionDoubleClicked( FSearchResult Item ); + void OnTreeSelectionDoubleClicked(FSearchResult Item); - /** Called when whether find in sub graph changed */ - void OnFindInSubGraphStateChanged(ECheckBoxState CheckBoxState); + /** Called when scope selection changed */ + void OnScopeChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo); + + /** Called when max depth changed */ + void OnMaxDepthChanged(int32 NewDepth); /** Called when a new row is being generated */ TSharedRef OnGenerateRow(FSearchResult InItem, const TSharedRef& OwnerTable); /** Begins the search based on the SearchValue */ void InitiateSearch(); - - /** Find any results that contain all of the tokens */ - void MatchTokens(const TArray& Tokens); - /** Find if child contains all of the tokens and add a result accordingly */ - static void MatchTokensInChild(const TArray& Tokens, UEdGraphNode* Child, FSearchResult ParentNode); - + /** Build searchable string from node and its FlowNodeBase + AddOns */ + const TMap>* BuildCategoryStrings(UEdGraphNode* Node, int32 Depth) const; + /** Determines if a string matches the search tokens */ static bool StringMatchesSearchTokens(const TArray& Tokens, const FString& ComparisonString); + static bool StringSetMatchesSearchTokens(const TArray& Tokens, const TSet& StringSet); + + /** Generate widget for scope combo */ + TSharedRef GenerateScopeWidget(TSharedPtr Item) const; + + /** Get current scope display text */ + FText GetCurrentScopeText() const; + + bool ProcessAsset(UFlowAsset* Asset, FSearchResult ParentResult, const TArray& Tokens, int32 Depth); + + bool RecurseIntoSubgraphsIfEnabled(UEdGraphNode* EdNode, FSearchResult ParentResult, const TArray& Tokens, int32 Depth); -private: + void UpdateSearchFlagToStringMapForEdGraphNode(const UEdGraphNode& EdGraphNode, TMap>& SearchFlagToStringMap, int32 Depth) const; + void UpdateSearchFlagToStringMapForFlowNodeBase(const UFlowNodeBase& FlowNodeBase, TMap>& SearchFlagToStringMap, int32 Depth) const; + void AppendPropertyValues(const void* Container, const UStruct* Struct, const UObject* ParentObject, TMap>& SearchFlagToStringMap, int32 Depth) const; + +protected: /** Pointer back to the flow editor that owns us */ TWeakPtr FlowAssetEditorPtr; - + /** The tree view displays the results */ TSharedPtr TreeView; /** The search text box */ TSharedPtr SearchTextField; - - /** This buffer stores the currently displayed results */ - TArray ItemsFound; - /** we need to keep a handle on the root result, because it won't show up in the tree */ - FSearchResult RootSearchResult; + /** The search button */ + TSharedPtr SearchButton; + + /** Struct with all of the search results */ + FFindInFlowAllResults SearchResults; + + /** Repeat Search Caching */ + FFindInFlowCache SearchCache; /** The string to highlight in the results */ FText HighlightText; /** The string to search for */ - FString SearchValue; + FString SearchValue; - /** Using to control whether search in sub graph */ - bool bFindInSubGraph = false; -}; + /** Search configuration */ + EFlowSearchFlags SearchFlags = EFlowSearchFlags::DefaultSearchFlags; + + TSharedPtr> MaxDepthSpinBox; + int32 MaxSearchDepth = 3; + + /** Scope selection */ + TArray> ScopeOptionList; + TSharedPtr SelectedScopeOption; + EFlowSearchScope SearchScope = EFlowSearchScope::ThisAssetOnly; +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/Find/FindInFlowEnums.h b/Source/FlowEditor/Public/Find/FindInFlowEnums.h new file mode 100644 index 000000000..e0c7a6887 --- /dev/null +++ b/Source/FlowEditor/Public/Find/FindInFlowEnums.h @@ -0,0 +1,48 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Types/FlowEnumUtils.h" + +#include "FindInFlowEnums.generated.h" + +/** Bitflags controlling what parts of a Flow node are included in search */ +UENUM(Meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true")) +enum class EFlowSearchFlags : uint32 +{ + None = 0 UMETA(Hidden), + + Titles = 1 << 0 UMETA(DisplayName = "Titles"), + Tooltips = 1 << 1 UMETA(DisplayName = "Tooltips"), + Classes = 1 << 2 UMETA(DisplayName = "Classes"), + Comments = 1 << 3 UMETA(DisplayName = "Comments"), + Descriptions = 1 << 4 UMETA(DisplayName = "Descriptions"), + ConfigText = 1 << 5 UMETA(DisplayName = "Config Text"), + PropertyNames = 1 << 6 UMETA(DisplayName = "Property Names"), + PropertyValues = 1 << 7 UMETA(DisplayName = "Property Values"), + AddOns = 1 << 8 UMETA(DisplayName = "Add-Ons"), + Subgraphs = 1 << 9 UMETA(DisplayName = "Subgraphs"), + + All = + Titles | Tooltips | Classes | Comments | Descriptions | ConfigText | + PropertyNames | PropertyValues | AddOns | Subgraphs UMETA(Hidden), + + // Default mask — used at startup and for "reset" + DefaultSearchFlags = All UMETA(Hidden), + PropertiesFlags = PropertyNames | PropertyValues | Tooltips UMETA(Hidden), +}; +ENUM_CLASS_FLAGS(EFlowSearchFlags); + +/** Search scope — intentionally minimal */ +UENUM() +enum class EFlowSearchScope : uint8 +{ + ThisAssetOnly UMETA(DisplayName = "This Asset", ToolTip = "Search only the currently open Flow Asset"), + AllOfThisType UMETA(DisplayName = "All Flow Assets of This Type",ToolTip = "Search all Flow Assets of this type (or subclasses)"), + AllFlowAssets UMETA(DisplayName = "All Flow Assets", ToolTip = "Search every Flow Asset in the project"), + + Max UMETA(Hidden), + Invalid UMETA(Hidden), + Min = 0 UMETA(Hidden), +}; +FLOW_ENUM_RANGE_VALUES(EFlowSearchScope); \ No newline at end of file diff --git a/Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h b/Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h new file mode 100644 index 000000000..49843b068 --- /dev/null +++ b/Source/FlowEditor/Public/Find/SFindInFlowFilterPopup.h @@ -0,0 +1,42 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SBoxPanel.h" + +#include "FindInFlowEnums.h" + +DECLARE_DELEGATE_OneParam(FFindInFlowApplyDelegate, EFlowSearchFlags); + +class SFindInFlowFilterPopup : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SFindInFlowFilterPopup) {} + SLATE_ARGUMENT(FFindInFlowApplyDelegate, OnApply) + SLATE_ARGUMENT(FFindInFlowApplyDelegate, OnSaveAsDefault) + SLATE_ARGUMENT(EFlowSearchFlags, InitialFlags) + SLATE_END_ARGS() + + void Construct(const FArguments& InArgs); + +protected: + + EFlowSearchFlags ProposedFlags = EFlowSearchFlags::DefaultSearchFlags; + + FFindInFlowApplyDelegate OnApplyDelegate; + FFindInFlowApplyDelegate OnSaveAsDefaultDelegate; + + TSharedPtr CheckBoxContainer; + + ECheckBoxState GetCheckState(EFlowSearchFlags Flag) const + { + return EnumHasAnyFlags(ProposedFlags, Flag) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + + FReply OnApplyClicked(); + FReply OnCancelClicked(); + FReply OnToggleAllClicked(); + FReply OnSaveAsDefaultClicked(); +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/FlowEditorModule.h b/Source/FlowEditor/Public/FlowEditorModule.h index 8dec688cf..e1266b1b3 100644 --- a/Source/FlowEditor/Public/FlowEditorModule.h +++ b/Source/FlowEditor/Public/FlowEditorModule.h @@ -34,6 +34,8 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen TSharedPtr MenuExtensibilityManager; TSharedPtr ToolBarExtensibilityManager; + bool bIsRegisteredForAssetChanges = false; + public: virtual void StartupModule() override; virtual void ShutdownModule() override; @@ -41,6 +43,8 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen virtual TSharedPtr GetMenuExtensibilityManager() override { return MenuExtensibilityManager; } virtual TSharedPtr GetToolBarExtensibilityManager() override { return ToolBarExtensibilityManager; } + void RegisterForAssetChanges(); + private: void TrySetFlowNodeDisplayStyleDefaults() const; @@ -65,4 +69,7 @@ class FLOWEDITOR_API FFlowEditorModule : public IModuleInterface, public IHasMen public: static TSharedRef CreateFlowAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UFlowAsset* FlowAsset); -}; + + void OnAssetUpdated(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& OldObjectPath); +}; \ No newline at end of file diff --git a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h index f19656734..8695702ca 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphEditorSettings.h @@ -3,6 +3,8 @@ #pragma once #include "Engine/DeveloperSettings.h" +#include "Find/FindInFlowEnums.h" + #include "FlowGraphEditorSettings.generated.h" UENUM() @@ -67,6 +69,14 @@ class FLOWEDITOR_API UFlowGraphEditorSettings : public UDeveloperSettings UPROPERTY(EditAnywhere, config, Category = "Wires") bool bHighlightOutputWiresOfSelectedNodes; + // Default search filter flags for the Flow Editor + UPROPERTY(VisibleAnywhere, config, Category = "Search", meta = (Bitmask, BitmaskEnum = "/Script/Flow.EFlowSearchFlags")) + uint32 DefaultSearchFlags = uint32(EFlowSearchFlags::DefaultSearchFlags); + + // Max search depth for inline objects in the Flow Editor + UPROPERTY(EditAnywhere, config, Category = "Search", meta = (ClampMin = 1)) + int32 DefaultMaxSearchDepth = 1; + public: virtual FName GetCategoryName() const override { return FName("Flow Graph"); } virtual FText GetSectionText() const override { return INVTEXT("User Settings"); } diff --git a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h index a912b60a4..78918f786 100644 --- a/Source/FlowEditor/Public/Graph/FlowGraphSchema.h +++ b/Source/FlowEditor/Public/Graph/FlowGraphSchema.h @@ -2,6 +2,9 @@ #pragma once +#include "Asset/FlowPinTypeMatchPolicy.h" +#include "Types/FlowPinTypeNamesStandard.h" + #include "EdGraph/EdGraphSchema.h" #include "Runtime/Launch/Resources/Version.h" #include "Templates/SubclassOf.h"