// 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 &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>) .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(); if (this->ComboActionEditorPtr.IsValid()) { FComboActionSearchManager::Get()->QuerySingleAction(SearchFilter, this->ComboActionEditorPtr.Pin()->GetEditingGraphSafe(), this->RootSearchResult); const TArray> &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 TempChild = Children[0]; this->RootSearchResult = TempChild; this->RootSearchResult->ClearParent(); } } this->ItemsFound = this->RootSearchResult->GetChildren(); if (this->ItemsFound.Num() == 0) { this->ItemsFound.Add(MakeShared(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 HostTabPtr = this->HostTab.Pin(); if (HostTabPtr.IsValid()) { return HostTabPtr->GetLayoutIdentifier().TabType; } return NAME_None; } void SComboActionSearch::CloseHostTab() { const TSharedPtr HostTabPtr = this->HostTab.Pin(); if (HostTabPtr.IsValid()) { HostTabPtr->RequestCloseTab(); } } void SComboActionSearch::HandleHostTabClosed(TSharedRef 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 InItem, TArray> &OutChildren) { OutChildren += InItem->GetChildren(); } void SComboActionSearch::HandleTreeSelectionDoubleClicked(TSharedPtr Item) { if (Item.IsValid()) { Item->OnClick(this->ComboActionEditorPtr); } } TSharedRef SComboActionSearch::HandleGenerateRow(TSharedPtr InItem, const TSharedRef &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>, 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 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 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