// Copyright 1998-2019 Epic Games, Inc. All Rights Reservekd. #include "SSearchableComboBox.h" #include "DetailLayoutBuilder.h" #define LOCTEXT_NAMESPACE "SearchableComboBox" void SSearchableComboBox::Construct(const FArguments& InArgs) { check(InArgs._ComboBoxStyle); ItemStyle = InArgs._ItemStyle; // Work out which values we should use based on whether we were given an override, or should use the style's version const FComboButtonStyle& OurComboButtonStyle = InArgs._ComboBoxStyle->ComboButtonStyle; const FButtonStyle* const OurButtonStyle = InArgs._ButtonStyle ? InArgs._ButtonStyle : &OurComboButtonStyle.ButtonStyle; this->OnComboBoxOpening = InArgs._OnComboBoxOpening; this->OnSelectionChanged = InArgs._OnSelectionChanged; this->OnGenerateWidget = InArgs._OnGenerateWidget; OptionsSource = InArgs._OptionsSource; CustomScrollbar = InArgs._CustomScrollbar; TSharedRef ComboBoxMenuContent = SNew(SBox) .MaxDesiredHeight(InArgs._MaxListHeight) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SAssignNew(this->SearchField, SEditableTextBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .HintText(LOCTEXT("Search", "Search")) .OnTextChanged(this, &SSearchableComboBox::OnSearchTextChanged) ] + SVerticalBox::Slot() [ SAssignNew(this->ComboListView, SComboListType) .ListItemsSource(OptionsSource) .OnGenerateRow(this, &SSearchableComboBox::GenerateMenuItemRow) .OnSelectionChanged(this, &SSearchableComboBox::OnSelectionChanged_Internal) .SelectionMode(ESelectionMode::Single) .ExternalScrollbar(InArgs._CustomScrollbar) ] ]; // Set up content TSharedPtr ButtonContent = InArgs._Content.Widget; if (InArgs._Content.Widget == SNullWidget::NullWidget) { SAssignNew(ButtonContent, STextBlock) .Text(NSLOCTEXT("SSearchableComboBox", "ContentWarning", "No Content Provided")) .ColorAndOpacity(FLinearColor::Red); } SComboButton::Construct(SComboButton::FArguments() .ComboButtonStyle(&OurComboButtonStyle) .ButtonStyle(OurButtonStyle) .Method(InArgs._Method) .ButtonContent() [ ButtonContent.ToSharedRef() ] .MenuContent() [ ComboBoxMenuContent ] .HasDownArrow(InArgs._HasDownArrow) .ContentPadding(InArgs._ContentPadding) .ForegroundColor(InArgs._ForegroundColor) .OnMenuOpenChanged(this, &SSearchableComboBox::OnMenuOpenChanged) .IsFocusable(true) ); SetMenuContentWidgetToFocus(ComboListView); // Need to establish the selected item at point of construction so its available for querying // NB: If you need a selection to fire use SetItemSelection rather than setting an IntiallySelectedItem SelectedItem = InArgs._InitiallySelectedItem; if (TListTypeTraits>::IsPtrValid(SelectedItem)) { ComboListView->Private_SetItemSelection(SelectedItem, true); } } void SSearchableComboBox::ClearSelection() { ComboListView->ClearSelection(); } void SSearchableComboBox::SetSelectedItem(TSharedPtr InSelectedItem) { if (TListTypeTraits>::IsPtrValid(InSelectedItem)) { ComboListView->SetSelection(InSelectedItem); } else { ComboListView->ClearSelection(); } } TSharedPtr SSearchableComboBox::GetSelectedItem() { return SelectedItem; } void SSearchableComboBox::RefreshOptions() { if (!ComboListView->IsPendingRefresh()) { ComboListView->RequestListRefresh(); } } TSharedRef SSearchableComboBox::GenerateMenuItemRow(TSharedPtr InItem, const TSharedRef& OwnerTable) { if (OnGenerateWidget.IsBound()) { FString SearchToken = SearchField->GetText().ToString().ToLower(); EVisibility WidgetVisibility = EVisibility::Visible; if (!SearchToken.IsEmpty()) { if (InItem->ToLower().Find(SearchToken) < 0) { WidgetVisibility = EVisibility::Collapsed; } } return SNew(SComboRow>, OwnerTable) .Style(ItemStyle) .Visibility(WidgetVisibility) [ OnGenerateWidget.Execute(InItem) ]; } else { return SNew(SComboRow>, OwnerTable) [ SNew(STextBlock).Text(NSLOCTEXT("SlateCore", "ComboBoxMissingOnGenerateWidgetMethod", "Please provide a .OnGenerateWidget() handler.")) ]; } } void SSearchableComboBox::OnMenuOpenChanged(bool bOpen) { if (bOpen == false) { if (TListTypeTraits>::IsPtrValid(SelectedItem)) { // Ensure the ListView selection is set back to the last committed selection ComboListView->SetSelection(SelectedItem, ESelectInfo::OnNavigation); ComboListView->RequestScrollIntoView(SelectedItem, 0); } // Set focus back to ComboBox for users focusing the ListView that just closed FSlateApplication::Get().ForEachUser([&](FSlateUser* User) { if (FSlateApplication::Get().HasUserFocusedDescendants(AsShared(), User->GetUserIndex())) { FSlateApplication::Get().SetUserFocus(User->GetUserIndex(), AsShared(), EFocusCause::SetDirectly); } }); } } void SSearchableComboBox::OnSelectionChanged_Internal(TSharedPtr ProposedSelection, ESelectInfo::Type SelectInfo) { // Ensure that the proposed selection is different if (SelectInfo != ESelectInfo::OnNavigation) { // Ensure that the proposed selection is different from selected if (ProposedSelection != SelectedItem) { SelectedItem = ProposedSelection; OnSelectionChanged.ExecuteIfBound(ProposedSelection, SelectInfo); } // close combo even if user reselected item this->SetIsOpen(false); } } void SSearchableComboBox::OnSearchTextChanged(const FText& ChangedText) { FString SearchToken = ChangedText.ToString().ToLower(); for (int32 i = 0; i < OptionsSource->Num(); i++) { TSharedPtr Row = ComboListView->WidgetFromItem((*OptionsSource)[i]); if (Row) { if (SearchToken.IsEmpty()) { Row->AsWidget()->SetVisibility(EVisibility::Visible); } else if ((*OptionsSource)[i]->ToLower().Find(SearchToken) >= 0) { Row->AsWidget()->SetVisibility(EVisibility::Visible); } else { Row->AsWidget()->SetVisibility(EVisibility::Collapsed); } } } ComboListView->RequestListRefresh(); SelectedItem = TSharedPtr< FString >(); } FReply SSearchableComboBox::OnButtonClicked() { // if user clicked to close the combo menu if (this->IsOpen()) { // Re-select first selected item, just in case it was selected by navigation previously TArray> SelectedItems = ComboListView->GetSelectedItems(); if (SelectedItems.Num() > 0) { OnSelectionChanged_Internal(SelectedItems[0], ESelectInfo::Direct); } } else { SearchField->SetText(FText::GetEmpty()); OnComboBoxOpening.ExecuteIfBound(); } return SComboButton::OnButtonClicked(); } #undef LOCTEXT_NAMESPACE