// Copyright Epic Games, Inc. All Rights Reserved. #include "Widgets/Input/SEditableTextBox.h" #include "Framework/Text/TextEditHelper.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Notifications/SPopUpErrorText.h" #if WITH_ACCESSIBILITY #include "Widgets/Accessibility/SlateAccessibleWidgets.h" #endif SEditableTextBox::SEditableTextBox() { #if WITH_ACCESSIBILITY AccessibleBehavior = EAccessibleBehavior::Auto; bCanChildrenBeAccessible = false; #endif } SEditableTextBox::~SEditableTextBox() = default; /** * Construct this widget * * @param InArgs The declaration data for this widget */ void SEditableTextBox::Construct( const FArguments& InArgs ) { check (InArgs._Style); SetStyle(InArgs._Style); PaddingOverride = InArgs._Padding; FontOverride = InArgs._Font; ForegroundColorOverride = InArgs._ForegroundColor; BackgroundColorOverride = InArgs._BackgroundColor; ReadOnlyForegroundColorOverride = InArgs._ReadOnlyForegroundColor; FocusedForegroundColorOverride = InArgs._FocusedForegroundColor; MaximumLength = InArgs._MaximumLength; OnTextChanged = InArgs._OnTextChanged; OnVerifyTextChanged = InArgs._OnVerifyTextChanged; OnTextCommitted = InArgs._OnTextCommitted; SBorder::Construct( SBorder::FArguments() .BorderImage( this, &SEditableTextBox::GetBorderImage ) .BorderBackgroundColor( this, &SEditableTextBox::DetermineBackgroundColor ) .ForegroundColor( this, &SEditableTextBox::DetermineForegroundColor ) .Padding(0.f) [ SAssignNew( Box, SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Fill) .HAlign(HAlign_Fill) .FillWidth(1) [ SAssignNew(PaddingBox, SBox) .Padding( this, &SEditableTextBox::DeterminePadding ) .VAlign(VAlign_Center) [ SAssignNew( EditableText, SEditableText ) .Text( InArgs._Text ) .HintText( InArgs._HintText ) .SearchText( InArgs._SearchText ) .Font( this, &SEditableTextBox::DetermineFont ) .IsReadOnly( InArgs._IsReadOnly ) .IsPassword( InArgs._IsPassword ) .IsCaretMovedWhenGainFocus( InArgs._IsCaretMovedWhenGainFocus ) .SelectAllTextWhenFocused( InArgs._SelectAllTextWhenFocused ) .RevertTextOnEscape( InArgs._RevertTextOnEscape ) .ClearKeyboardFocusOnCommit( InArgs._ClearKeyboardFocusOnCommit ) .Justification( InArgs._Justification ) .AllowContextMenu( InArgs._AllowContextMenu ) .OnContextMenuOpening( InArgs._OnContextMenuOpening ) .ContextMenuExtender( InArgs._ContextMenuExtender ) .OnBeginTextEdit(InArgs._OnBeginTextEdit) .OnTextChanged(this, &SEditableTextBox::OnEditableTextChanged) .OnTextCommitted(this, &SEditableTextBox::OnEditableTextCommitted) .MinDesiredWidth( InArgs._MinDesiredWidth ) .SelectAllTextOnCommit( InArgs._SelectAllTextOnCommit ) .SelectWordOnMouseDoubleClick(InArgs._SelectWordOnMouseDoubleClick) .OnKeyCharHandler( InArgs._OnKeyCharHandler ) .OnKeyDownHandler( InArgs._OnKeyDownHandler ) .VirtualKeyboardType( InArgs._VirtualKeyboardType ) .VirtualKeyboardOptions( InArgs._VirtualKeyboardOptions ) .VirtualKeyboardTrigger( InArgs._VirtualKeyboardTrigger ) .VirtualKeyboardDismissAction( InArgs._VirtualKeyboardDismissAction ) .TextShapingMethod(InArgs._TextShapingMethod) .TextFlowDirection( InArgs._TextFlowDirection ) .OverflowPolicy(InArgs._OverflowPolicy) ] ] ] ); ErrorReporting = InArgs._ErrorReporting; if ( ErrorReporting.IsValid() ) { Box->AddSlot() .AutoWidth() .Padding(3,0) [ ErrorReporting->AsWidget() ]; } } void SEditableTextBox::SetStyle(const FEditableTextBoxStyle* InStyle) { Style = InStyle; if ( Style == nullptr ) { FArguments Defaults; Style = Defaults._Style; } check(Style); BorderImageNormal = &Style->BackgroundImageNormal; BorderImageHovered = &Style->BackgroundImageHovered; BorderImageFocused = &Style->BackgroundImageFocused; BorderImageReadOnly = &Style->BackgroundImageReadOnly; SetTextBlockStyle(&Style->TextStyle); Invalidate(EInvalidateWidgetReason::Layout); } void SEditableTextBox::SetTextBlockStyle(const FTextBlockStyle* InTextStyle) { // The Construct() function will call this before EditableText exists, // so we need a guard here to ignore that function call. if (EditableText.IsValid()) { EditableText->SetTextBlockStyle(InTextStyle); } } void SEditableTextBox::SetText( const TAttribute< FText >& InNewText ) { EditableText->SetText( InNewText ); } void SEditableTextBox::SetError( const FText& InError ) { SetError( InError.ToString() ); } void SEditableTextBox::SetError( const FString& InError ) { const bool bHaveError = !InError.IsEmpty(); if (!bHaveError) { if (ErrorReporting.IsValid()) { ErrorReporting->AsWidget()->SetVisibility(EVisibility::Collapsed); ErrorReporting->SetError(FString()); } return; } if ( !ErrorReporting.IsValid() ) { // No error reporting was specified; make a default one TSharedPtr ErrorTextWidget; Box->AddSlot() .AutoWidth() .Padding(3,0) [ SAssignNew( ErrorTextWidget, SPopupErrorText ) ]; ErrorReporting = ErrorTextWidget; } ErrorReporting->AsWidget()->SetVisibility(EVisibility::Visible); ErrorReporting->SetError( InError ); } void SEditableTextBox::SetOnKeyCharHandler(FOnKeyChar InOnKeyCharHandler) { EditableText->SetOnKeyCharHandler(InOnKeyCharHandler); } void SEditableTextBox::SetOnKeyDownHandler(FOnKeyDown InOnKeyDownHandler) { EditableText->SetOnKeyDownHandler(InOnKeyDownHandler); } void SEditableTextBox::SetTextShapingMethod(const TOptional& InTextShapingMethod) { EditableText->SetTextShapingMethod(InTextShapingMethod); } void SEditableTextBox::SetTextFlowDirection(const TOptional& InTextFlowDirection) { EditableText->SetTextFlowDirection(InTextFlowDirection); } void SEditableTextBox::SetOverflowPolicy(TOptional InOverflowPolicy) { EditableText->SetOverflowPolicy(InOverflowPolicy); } bool SEditableTextBox::AnyTextSelected() const { return EditableText->AnyTextSelected(); } void SEditableTextBox::SelectAllText() { EditableText->SelectAllText(); } void SEditableTextBox::ClearSelection() { EditableText->ClearSelection(); } FText SEditableTextBox::GetSelectedText() const { return EditableText->GetSelectedText(); } void SEditableTextBox::GoTo(const FTextLocation& NewLocation) { EditableText->GoTo(NewLocation); } void SEditableTextBox::ScrollTo(const FTextLocation& NewLocation) { EditableText->ScrollTo(NewLocation); } void SEditableTextBox::BeginSearch(const FText& InSearchText, const ESearchCase::Type InSearchCase, const bool InReverse) { EditableText->BeginSearch(InSearchText, InSearchCase, InReverse); } void SEditableTextBox::AdvanceSearch(const bool InReverse) { EditableText->AdvanceSearch(InReverse); } bool SEditableTextBox::HasError() const { return ErrorReporting.IsValid() && ErrorReporting->HasError(); } const FSlateBrush* SEditableTextBox::GetBorderImage() const { if ( EditableText->IsTextReadOnly() ) { return BorderImageReadOnly; } else if ( EditableText->HasKeyboardFocus() ) { return BorderImageFocused; } else { if ( EditableText->IsHovered() ) { return BorderImageHovered; } else { return BorderImageNormal; } } } bool SEditableTextBox::SupportsKeyboardFocus() const { return StaticCastSharedPtr(EditableText)->SupportsKeyboardFocus(); } bool SEditableTextBox::HasKeyboardFocus() const { // Since keyboard focus is forwarded to our editable text, we will test it instead return SBorder::HasKeyboardFocus() || EditableText->HasKeyboardFocus(); } FReply SEditableTextBox::OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent ) { FReply Reply = FReply::Handled(); if ( InFocusEvent.GetCause() != EFocusCause::Cleared ) { // Forward keyboard focus to our editable text widget Reply.SetUserFocus(EditableText.ToSharedRef(), InFocusEvent.GetCause()); } return Reply; } FReply SEditableTextBox::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Escape && EditableText->HasKeyboardFocus()) { // Clear focus return FReply::Handled().SetUserFocus(SharedThis(this), EFocusCause::Cleared); } return FReply::Unhandled(); } FMargin SEditableTextBox::DeterminePadding() const { check(Style); return PaddingOverride.IsSet() ? PaddingOverride.Get() : Style->Padding; } FSlateFontInfo SEditableTextBox::DetermineFont() const { check(Style); return FontOverride.IsSet() ? FontOverride.Get() : Style->TextStyle.Font; } FSlateColor SEditableTextBox::DetermineBackgroundColor() const { check(Style); return BackgroundColorOverride.IsSet() ? BackgroundColorOverride.Get() : Style->BackgroundColor; } FSlateColor SEditableTextBox::DetermineForegroundColor() const { check(Style); if (EditableText->IsTextReadOnly()) { if (ReadOnlyForegroundColorOverride.IsSet()) { return ReadOnlyForegroundColorOverride.Get(); } if (ForegroundColorOverride.IsSet()) { return ForegroundColorOverride.Get(); } return Style->ReadOnlyForegroundColor; } else if(HasKeyboardFocus()) { return FocusedForegroundColorOverride.IsSet() ? FocusedForegroundColorOverride.Get() : Style->FocusedForegroundColor; } else { return ForegroundColorOverride.IsSet() ? ForegroundColorOverride.Get() : Style->ForegroundColor; } } void SEditableTextBox::SetHintText(const TAttribute< FText >& InHintText) { EditableText->SetHintText(InHintText); } void SEditableTextBox::SetSearchText(const TAttribute& InSearchText) { EditableText->SetSearchText(InSearchText); } FText SEditableTextBox::GetSearchText() const { return EditableText->GetSearchText(); } void SEditableTextBox::SetIsReadOnly(TAttribute< bool > InIsReadOnly) { EditableText->SetIsReadOnly(InIsReadOnly); } void SEditableTextBox::SetIsPassword(TAttribute< bool > InIsPassword) { EditableText->SetIsPassword(InIsPassword); } void SEditableTextBox::SetFont(const TAttribute& InFont) { FontOverride = InFont; } void SEditableTextBox::SetTextBoxForegroundColor(const TAttribute& InForegroundColor) { ForegroundColorOverride = InForegroundColor; } void SEditableTextBox::SetTextBoxBackgroundColor(const TAttribute& InBackgroundColor) { BackgroundColorOverride = InBackgroundColor; } void SEditableTextBox::SetReadOnlyForegroundColor(const TAttribute& InReadOnlyForegroundColor) { ReadOnlyForegroundColorOverride = InReadOnlyForegroundColor; } void SEditableTextBox::SetFocusedForegroundColor(const TAttribute& InFocusedForegroundColor) { FocusedForegroundColorOverride = InFocusedForegroundColor; } void SEditableTextBox::SetMaximumLength(const TAttribute& InMaximumLength) { MaximumLength = InMaximumLength; } void SEditableTextBox::SetMinimumDesiredWidth(const TAttribute& InMinimumDesiredWidth) { EditableText->SetMinDesiredWidth(InMinimumDesiredWidth); } void SEditableTextBox::SetIsCaretMovedWhenGainFocus(const TAttribute& InIsCaretMovedWhenGainFocus) { EditableText->SetIsCaretMovedWhenGainFocus(InIsCaretMovedWhenGainFocus); } void SEditableTextBox::SetSelectAllTextWhenFocused(const TAttribute& InSelectAllTextWhenFocused) { EditableText->SetSelectAllTextWhenFocused(InSelectAllTextWhenFocused); } void SEditableTextBox::SetRevertTextOnEscape(const TAttribute& InRevertTextOnEscape) { EditableText->SetRevertTextOnEscape(InRevertTextOnEscape); } void SEditableTextBox::SetClearKeyboardFocusOnCommit(const TAttribute& InClearKeyboardFocusOnCommit) { EditableText->SetClearKeyboardFocusOnCommit(InClearKeyboardFocusOnCommit); } void SEditableTextBox::SetSelectAllTextOnCommit(const TAttribute& InSelectAllTextOnCommit) { EditableText->SetSelectAllTextOnCommit(InSelectAllTextOnCommit); } void SEditableTextBox::SetSelectWordOnMouseDoubleClick(const TAttribute& InSelectWordOnMouseDoubleClick) { EditableText->SetSelectWordOnMouseDoubleClick(InSelectWordOnMouseDoubleClick); } void SEditableTextBox::SetJustification(const TAttribute& InJustification) { EditableText->SetJustification(InJustification); } void SEditableTextBox::SetAllowContextMenu(TAttribute InAllowContextMenu) { EditableText->SetAllowContextMenu(InAllowContextMenu); } void SEditableTextBox::SetVirtualKeyboardDismissAction(TAttribute InVirtualKeyboardDismissAction) { EditableText->SetVirtualKeyboardDismissAction(InVirtualKeyboardDismissAction); } void SEditableTextBox::EnableTextInputMethodContext() { EditableText->EnableTextInputMethodContext(); } #if WITH_ACCESSIBILITY TSharedRef SEditableTextBox::CreateAccessibleWidget() { return MakeShareable(new FSlateAccessibleEditableTextBox(SharedThis(this))); } TOptional SEditableTextBox::GetDefaultAccessibleText(EAccessibleType AccessibleType) const { // The parent Construct() function will call this before EditableText exists, // so we need a guard here to ignore that function call. if (EditableText.IsValid()) { return EditableText->GetHintText(); } return TOptional(); } #endif void SEditableTextBox::OnEditableTextChanged(const FText& InText) { OnTextChanged.ExecuteIfBound(InText); const int32 MaximumLengthValue = MaximumLength.Get(); if (OnVerifyTextChanged.IsBound() || MaximumLengthValue >= 0) { FText OutErrorMessage; if (!FTextEditHelper::VerifyTextLength(InText, OutErrorMessage, MaximumLengthValue) || (OnVerifyTextChanged.IsBound() && !OnVerifyTextChanged.Execute(InText, OutErrorMessage))) { // Display as an error. SetError(OutErrorMessage); } else { SetError(FText::GetEmpty()); } } } void SEditableTextBox::OnEditableTextCommitted(const FText& InText, ETextCommit::Type InCommitType) { FText OutErrorMessage; if (!FTextEditHelper::VerifyTextLength(InText, OutErrorMessage, MaximumLength.Get()) || (OnVerifyTextChanged.IsBound() && !OnVerifyTextChanged.Execute(InText, OutErrorMessage))) { // Display as an error. if (InCommitType == ETextCommit::OnEnter) { SetError(OutErrorMessage); } return; } // Text commited without errors, so clear error text SetError(FText::GetEmpty()); OnTextCommitted.ExecuteIfBound(InText, InCommitType); }