312 lines
9.8 KiB
C++
312 lines
9.8 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Widgets/Images/SThrobber.h"
|
|
#include "Widgets/Layout/SSpacer.h"
|
|
|
|
void SSearchBox::Construct( const FArguments& InArgs )
|
|
{
|
|
check(InArgs._Style);
|
|
|
|
SearchResultData = InArgs._SearchResultData;
|
|
bIsSearching = InArgs._IsSearching;
|
|
OnSearchDelegate = InArgs._OnSearch;
|
|
OnTextChangedDelegate = InArgs._OnTextChanged;
|
|
OnTextCommittedDelegate = InArgs._OnTextCommitted;
|
|
DelayChangeNotificationsWhileTyping = InArgs._DelayChangeNotificationsWhileTyping;
|
|
DelayChangeNotificationsWhileTypingSeconds = InArgs._DelayChangeNotificationsWhileTypingSeconds;
|
|
|
|
InactiveFont = InArgs._Style->TextBoxStyle.TextStyle.Font;
|
|
ActiveFont = InArgs._Style->ActiveFontInfo;
|
|
|
|
SEditableTextBox::Construct( SEditableTextBox::FArguments()
|
|
.Style( &InArgs._Style->TextBoxStyle )
|
|
.Font( this, &SSearchBox::GetWidgetFont )
|
|
.Text( InArgs._InitialText.Get() )
|
|
.HintText( InArgs._HintText )
|
|
.SelectAllTextWhenFocused( InArgs._SelectAllTextWhenFocused )
|
|
.RevertTextOnEscape( true )
|
|
.ClearKeyboardFocusOnCommit( false )
|
|
.OnTextChanged( this, &SSearchBox::HandleTextChanged )
|
|
.OnTextCommitted( this, &SSearchBox::HandleTextCommitted )
|
|
.OnVerifyTextChanged( InArgs._OnVerifyTextChanged )
|
|
.MinDesiredWidth( InArgs._MinDesiredWidth )
|
|
.OnKeyDownHandler( InArgs._OnKeyDownHandler )
|
|
);
|
|
|
|
// If we want to have the search result buttons appear to the left of the text box we have to insert the slots instead of add them
|
|
int32 SlotIndex = InArgs._Style->bLeftAlignSearchResultButtons ? 0 : Box->NumSlots();
|
|
|
|
// Add a throbber to show if there is a search running.
|
|
Box->InsertSlot(SlotIndex++)
|
|
.AutoWidth()
|
|
.Padding(0, 0, 2, 0)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SCircularThrobber)
|
|
.Radius(9.0f)
|
|
.Visibility(this, &SSearchBox::GetIsSearchingThrobberVisibility)
|
|
.ToolTipText(NSLOCTEXT("SearchBox", "Searching", "Searching..."))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
];
|
|
|
|
// If a search delegate was bound, add a previous and next button
|
|
if (OnSearchDelegate.IsBound())
|
|
{
|
|
FMargin SearchResultPadding = InArgs._Style->bLeftAlignSearchResultButtons ? FMargin(5, 0, 2, 0) : FMargin(2, 0, 2, 0);
|
|
|
|
// Search result data text
|
|
Box->InsertSlot(SlotIndex++)
|
|
.AutoWidth()
|
|
.Padding(SearchResultPadding)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Visibility(this, &SSearchBox::GetSearchResultDataVisibility)
|
|
.Text(this, &SSearchBox::GetSearchResultText)
|
|
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
|
|
];
|
|
|
|
// Previous result button
|
|
Box->InsertSlot(SlotIndex++)
|
|
.AutoWidth()
|
|
.Padding(InArgs._Style->ImagePadding)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle( FCoreStyle::Get(), "NoBorder" )
|
|
.ContentPadding( FMargin(5, 0) )
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked( this, &SSearchBox::OnClickedSearch, SSearchBox::Previous )
|
|
.ForegroundColor( FSlateColor::UseForeground() )
|
|
.IsFocusable(false)
|
|
.Visibility(this, &SSearchBox::GetSearchResultNavigationButtonVisibility)
|
|
[
|
|
SNew(SImage)
|
|
.Image( &InArgs._Style->UpArrowImage )
|
|
.ColorAndOpacity( FSlateColor::UseForeground() )
|
|
.DesiredSizeOverride( InArgs._Style->ImageSizeOverride )
|
|
]
|
|
];
|
|
// Next result button
|
|
Box->InsertSlot(SlotIndex++)
|
|
.AutoWidth()
|
|
.Padding(InArgs._Style->ImagePadding)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle( FCoreStyle::Get(), "NoBorder" )
|
|
.ContentPadding( FMargin(5, 0) )
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked( this, &SSearchBox::OnClickedSearch, SSearchBox::Next )
|
|
.ForegroundColor( FSlateColor::UseForeground() )
|
|
.IsFocusable(false)
|
|
.Visibility(this, &SSearchBox::GetSearchResultNavigationButtonVisibility)
|
|
[
|
|
SNew(SImage)
|
|
.Image( &InArgs._Style->DownArrowImage )
|
|
.ColorAndOpacity( FSlateColor::UseForeground() )
|
|
.DesiredSizeOverride( InArgs._Style->ImageSizeOverride )
|
|
]
|
|
];
|
|
}
|
|
|
|
// Add a search glass image so that the user knows this text box is for searching
|
|
int GlassImageSlotIndex = InArgs._Style->bLeftAlignGlassImageAndClearButton ? 0 : Box->NumSlots();
|
|
|
|
if (InArgs._Style->bLeftAlignSearchResultButtons == InArgs._Style->bLeftAlignGlassImageAndClearButton)
|
|
{
|
|
// if they are on the same side, Glass Image follows the search result buttons
|
|
GlassImageSlotIndex = SlotIndex;
|
|
}
|
|
|
|
const int32 ClearButtonIndex = GlassImageSlotIndex + 1;
|
|
|
|
Box->InsertSlot(GlassImageSlotIndex)
|
|
.AutoWidth()
|
|
.Padding(InArgs._Style->ImagePadding)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.Visibility(this, &SSearchBox::GetSearchGlassVisibility)
|
|
.Image(&InArgs._Style->GlassImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.DesiredSizeOverride(InArgs._Style->ImageSizeOverride)
|
|
];
|
|
|
|
// Add an X to clear the search whenever there is some text typed into it
|
|
Box->InsertSlot(ClearButtonIndex)
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.Visibility(this, &SSearchBox::GetXVisibility)
|
|
.ButtonStyle(FAppStyle::Get(), "ThinHoverOnlyButton")
|
|
.ContentPadding(InArgs._Style->ImagePadding)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked(this, &SSearchBox::OnClearSearch)
|
|
.AddMetaData<FTagMetaData>(TEXT("SearchClearButton"))
|
|
// Allow the button to steal focus so that the search text will be automatically committed. Afterwards focus will be returned to the text box.
|
|
// If the user is keyboard-centric, they'll "ctrl+a, delete" to clear the search
|
|
.IsFocusable(true)
|
|
[
|
|
SNew(SImage)
|
|
.Image(&InArgs._Style->ClearImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.DesiredSizeOverride(InArgs._Style->ImageSizeOverride)
|
|
]
|
|
];
|
|
|
|
// Padding at the end so that the text box grows long enough to host all buttons
|
|
// the text box needs extra space because it is rounded on both ends
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SSpacer)
|
|
.Size(FVector2d(7,0))
|
|
];
|
|
}
|
|
|
|
EActiveTimerReturnType SSearchBox::TriggerOnTextChanged( double, float, FText NewText )
|
|
{
|
|
// Reset the flag first in case the delegate winds up triggering HandleTextChanged
|
|
ActiveTimerHandle.Reset();
|
|
|
|
OnTextChangedDelegate.ExecuteIfBound( NewText );
|
|
return EActiveTimerReturnType::Stop;
|
|
}
|
|
|
|
void SSearchBox::HandleTextChanged(const FText& NewText)
|
|
{
|
|
// Remove the existing registered tick if necessary
|
|
if ( ActiveTimerHandle.IsValid() )
|
|
{
|
|
UnRegisterActiveTimer( ActiveTimerHandle.Pin().ToSharedRef() );
|
|
}
|
|
|
|
if ( DelayChangeNotificationsWhileTyping.Get() && HasKeyboardFocus() )
|
|
{
|
|
ActiveTimerHandle = RegisterActiveTimer( DelayChangeNotificationsWhileTypingSeconds.Get(), FWidgetActiveTimerDelegate::CreateSP( this, &SSearchBox::TriggerOnTextChanged, NewText ) );
|
|
}
|
|
else
|
|
{
|
|
OnTextChangedDelegate.ExecuteIfBound( NewText );
|
|
}
|
|
}
|
|
|
|
void SSearchBox::HandleTextCommitted(const FText& NewText, ETextCommit::Type CommitType)
|
|
{
|
|
if ( ActiveTimerHandle.IsValid() )
|
|
{
|
|
UnRegisterActiveTimer( ActiveTimerHandle.Pin().ToSharedRef() );
|
|
|
|
// If there was a pending text change we need to fire it in case someone was cache the last value and
|
|
// ignoring changes during commit, or if they expected a change always before the commit.
|
|
OnTextChangedDelegate.ExecuteIfBound(NewText);
|
|
}
|
|
|
|
OnTextCommittedDelegate.ExecuteIfBound( NewText, CommitType );
|
|
}
|
|
|
|
FText SSearchBox::GetSearchResultText() const
|
|
{
|
|
TOptional<FSearchResultData> CurrentSearchResultData = SearchResultData.Get();
|
|
if (CurrentSearchResultData.IsSet())
|
|
{
|
|
return FText::Format(NSLOCTEXT("SearchBox", "SearchResultFormat", "{0} / {1}"),
|
|
CurrentSearchResultData.GetValue().CurrentSearchResultIndex, CurrentSearchResultData.GetValue().NumSearchResults);
|
|
}
|
|
else
|
|
{
|
|
return FText();
|
|
}
|
|
}
|
|
|
|
EVisibility SSearchBox::GetSearchResultNavigationButtonVisibility() const
|
|
{
|
|
if (SearchResultData.IsBound())
|
|
{
|
|
return SearchResultData.Get().IsSet()
|
|
? EVisibility::Visible
|
|
: EVisibility::Collapsed;
|
|
}
|
|
else
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
}
|
|
|
|
EVisibility SSearchBox::GetXVisibility() const
|
|
{
|
|
return (EditableText->GetText().IsEmpty())
|
|
? EVisibility::Collapsed
|
|
: EVisibility::Visible;
|
|
}
|
|
|
|
EVisibility SSearchBox::GetSearchResultDataVisibility() const
|
|
{
|
|
return SearchResultData.Get().IsSet()
|
|
? EVisibility::Visible
|
|
: EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SSearchBox::GetIsSearchingThrobberVisibility() const
|
|
{
|
|
return (bIsSearching.Get())
|
|
? EVisibility::Visible
|
|
: EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SSearchBox::GetSearchGlassVisibility() const
|
|
{
|
|
return (EditableText->GetText().IsEmpty())
|
|
? EVisibility::Visible
|
|
: EVisibility::Collapsed;
|
|
}
|
|
|
|
FReply SSearchBox::OnClickedSearch(SSearchBox::SearchDirection Direction)
|
|
{
|
|
OnSearchDelegate.ExecuteIfBound(Direction);
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SSearchBox::OnClearSearch()
|
|
{
|
|
// When we get here, the button will already have stolen focus, thus committing any unset values in the search box.
|
|
// This will have allowed any widgets which depend on its state to update themselves prior to the search box being cleared,
|
|
// which happens now. This is important as the act of clearing the search text may also destroy those widgets (for example,
|
|
// if the search box is being used as a filter).
|
|
this->SetText( FText::GetEmpty() );
|
|
|
|
// Finally set focus back to the editable text
|
|
return FReply::Handled().SetUserFocus(EditableText.ToSharedRef(), EFocusCause::SetDirectly);
|
|
}
|
|
|
|
FSlateFontInfo SSearchBox::GetWidgetFont() const
|
|
{
|
|
return EditableText->GetText().IsEmpty() ? InactiveFont : ActiveFont;
|
|
}
|
|
|
|
FTextSelection SSearchBox::GetSelection() const
|
|
{
|
|
return EditableText->GetSelection();
|
|
}
|
|
|
|
void SSearchBox::SelectText(const FTextLocation& InSelectionStart, const FTextLocation& InCursorLocation)
|
|
{
|
|
EditableText->SelectText(InSelectionStart, InCursorLocation);
|
|
}
|