405 lines
11 KiB
C++

// 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