// Copyright Epic Games, Inc. All Rights Reserved. #include "Widgets/Input/SEditableText.h" #include "Framework/Text/TextEditHelper.h" #include "Framework/Text/PlainTextLayoutMarshaller.h" #include "Widgets/Text/SlateEditableTextLayout.h" #include "Types/ReflectionMetadata.h" #include "Types/TrackedMetaData.h" #if WITH_ACCESSIBILITY #include "Widgets/Accessibility/SlateAccessibleWidgets.h" #endif SEditableText::SEditableText() { #if WITH_ACCESSIBILITY AccessibleBehavior = EAccessibleBehavior::Auto; bCanChildrenBeAccessible = false; #endif } SEditableText::~SEditableText() { // Needed to avoid "deletion of pointer to incomplete type 'FSlateEditableTextLayout'; no destructor called" error when using TUniquePtr } void SEditableText::Construct( const FArguments& InArgs ) { bIsReadOnly = InArgs._IsReadOnly; bIsPassword = InArgs._IsPassword; bIsCaretMovedWhenGainFocus = InArgs._IsCaretMovedWhenGainFocus; bSelectAllTextWhenFocused = InArgs._SelectAllTextWhenFocused; bRevertTextOnEscape = InArgs._RevertTextOnEscape; bClearKeyboardFocusOnCommit = InArgs._ClearKeyboardFocusOnCommit; bAllowContextMenu = InArgs._AllowContextMenu; OnContextMenuOpening = InArgs._OnContextMenuOpening; OnIsTypedCharValid = InArgs._OnIsTypedCharValid; OnTextChangedCallback = InArgs._OnTextChanged; OnTextCommittedCallback = InArgs._OnTextCommitted; MinDesiredWidth = InArgs._MinDesiredWidth; bSelectAllTextOnCommit = InArgs._SelectAllTextOnCommit; bSelectWordOnMouseDoubleClick = InArgs._SelectWordOnMouseDoubleClick; VirtualKeyboardType = InArgs._VirtualKeyboardType; VirtualKeyboardOptions = InArgs._VirtualKeyboardOptions; VirtualKeyboardTrigger = InArgs._VirtualKeyboardTrigger; VirtualKeyboardDismissAction = InArgs._VirtualKeyboardDismissAction; OnKeyCharHandler = InArgs._OnKeyCharHandler; OnKeyDownHandler = InArgs._OnKeyDownHandler; bEnableIntegratedKeyboard = InArgs._EnableIntegratedKeyboard; Font = InArgs._Font; ColorAndOpacity = InArgs._ColorAndOpacity; BackgroundImageSelected = InArgs._BackgroundImageSelected; // We use the given style when creating the text layout as it may not be safe to call the override delegates until we've finished being constructed // The first call to SynchronizeTextStyle will apply the correct overrides, and that will happen before the first paint check(InArgs._Style); FTextBlockStyle TextStyle = FCoreStyle::Get().GetWidgetStyle("NormalText"); TextStyle.Font = InArgs._Style->Font; TextStyle.ColorAndOpacity = InArgs._Style->ColorAndOpacity; TextStyle.HighlightShape = InArgs._Style->BackgroundImageSelected; PlainTextMarshaller = FPlainTextLayoutMarshaller::Create(); PlainTextMarshaller->SetIsPassword(bIsPassword); // We use a separate marshaller for the hint text, as that should never be displayed as a password TSharedRef HintTextMarshaller = FPlainTextLayoutMarshaller::Create(); EditableTextLayout = MakeUnique(*this, InArgs._Text, TextStyle, InArgs._TextShapingMethod, InArgs._TextFlowDirection, FCreateSlateTextLayout(), PlainTextMarshaller.ToSharedRef(), HintTextMarshaller); EditableTextLayout->SetHintText(InArgs._HintText); EditableTextLayout->SetSearchText(InArgs._SearchText); EditableTextLayout->SetCursorBrush(InArgs._CaretImage.IsSet() ? InArgs._CaretImage : &InArgs._Style->CaretImage); EditableTextLayout->SetCompositionBrush(InArgs._BackgroundImageComposing.IsSet() ? InArgs._BackgroundImageComposing : &InArgs._Style->BackgroundImageComposing); EditableTextLayout->SetDebugSourceInfo(TAttribute::Create(TAttribute::FGetter::CreateLambda([this]{ return FReflectionMetaData::GetWidgetDebugInfo(this); }))); EditableTextLayout->SetJustification(InArgs._Justification); EditableTextLayout->SetOverflowPolicy(InArgs._OverflowPolicy); // build context menu extender MenuExtender = MakeShareable(new FExtender()); MenuExtender->AddMenuExtension("EditText", EExtensionHook::Before, TSharedPtr(), InArgs._ContextMenuExtender); AddMetadata(MakeShared(this, FName(TEXT("EditableText")))); } void SEditableText::SetText( const TAttribute< FText >& InNewText ) { EditableTextLayout->SetText(InNewText); } FText SEditableText::GetText() const { return EditableTextLayout->GetText(); } bool SEditableText::SetEditableText(const FText& InNewText) { return EditableTextLayout->SetEditableText(InNewText); } void SEditableText::SetFont( const TAttribute< FSlateFontInfo >& InNewFont ) { Font = InNewFont; Invalidate(EInvalidateWidgetReason::Layout); } FSlateFontInfo SEditableText::GetFont() const { return Font.Get(); } void SEditableText::SetTextStyle( const FEditableTextStyle& InNewTextStyle ) { Font = InNewTextStyle.Font; ColorAndOpacity = InNewTextStyle.ColorAndOpacity; BackgroundImageSelected = &InNewTextStyle.BackgroundImageSelected; Invalidate(EInvalidateWidgetReason::Layout); } void SEditableText::SetTextBlockStyle(const FTextBlockStyle* InTextStyle) { if (InTextStyle) { EditableTextLayout->SetTextStyle(*InTextStyle); Invalidate(EInvalidateWidgetReason::Layout); //Using Layout as changing text block size can affect the size. } } void SEditableText::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { EditableTextLayout->Tick(AllottedGeometry, InCurrentTime, InDeltaTime); } int32 SEditableText::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { const FTextBlockStyle& EditableTextStyle = EditableTextLayout->GetTextStyle(); const FLinearColor ForegroundColor = EditableTextStyle.ColorAndOpacity.GetColor(InWidgetStyle); FWidgetStyle TextWidgetStyle = FWidgetStyle(InWidgetStyle) .SetForegroundColor(ForegroundColor); LayerId = EditableTextLayout->OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, TextWidgetStyle, ShouldBeEnabled(bParentEnabled)); return LayerId; } void SEditableText::CacheDesiredSize(float LayoutScaleMultiplier) { SynchronizeTextStyle(); EditableTextLayout->CacheDesiredSize(LayoutScaleMultiplier); SWidget::CacheDesiredSize(LayoutScaleMultiplier); } FVector2D SEditableText::ComputeDesiredSize(float LayoutScaleMultiplier) const { FVector2D TextLayoutSize = EditableTextLayout->ComputeDesiredSize(LayoutScaleMultiplier); TextLayoutSize.X = FMath::Max(TextLayoutSize.X, MinDesiredWidth.Get()); return TextLayoutSize; } FChildren* SEditableText::GetChildren() { return EditableTextLayout->GetChildren(); } void SEditableText::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const { EditableTextLayout->OnArrangeChildren(AllottedGeometry, ArrangedChildren); } FReply SEditableText::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) { TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); if ( DragDropOp.IsValid() ) { if ( DragDropOp->HasText() ) { return FReply::Handled(); } } return FReply::Unhandled(); } FReply SEditableText::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent ) { TSharedPtr DragDropOp = DragDropEvent.GetOperationAs(); if ( DragDropOp.IsValid() ) { if ( DragDropOp->HasText() ) { EditableTextLayout->SetText(FText::FromString(DragDropOp->GetText())); return FReply::Handled(); } } return FReply::Unhandled(); } bool SEditableText::SupportsKeyboardFocus() const { return true; } FReply SEditableText::OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent ) { EditableTextLayout->HandleFocusReceived(InFocusEvent); return FReply::Handled(); } void SEditableText::OnFocusLost( const FFocusEvent& InFocusEvent ) { EditableTextLayout->HandleFocusLost(InFocusEvent); } FReply SEditableText::OnKeyChar( const FGeometry& MyGeometry, const FCharacterEvent& InCharacterEvent ) { FReply Reply = FReply::Unhandled(); // First call the user defined key handler, there might be overrides to normal functionality if (OnKeyCharHandler.IsBound()) { Reply = OnKeyCharHandler.Execute(MyGeometry, InCharacterEvent); } if (!Reply.IsEventHandled()) { Reply = EditableTextLayout->HandleKeyChar(InCharacterEvent); } return Reply; } FReply SEditableText::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { FReply Reply = FReply::Unhandled(); // First call the user defined key handler, there might be overrides to normal functionality if (OnKeyDownHandler.IsBound()) { Reply = OnKeyDownHandler.Execute(MyGeometry, InKeyEvent); } if (!Reply.IsEventHandled()) { Reply = EditableTextLayout->HandleKeyDown(InKeyEvent); if (!Reply.IsEventHandled()) { Reply = SWidget::OnKeyDown(MyGeometry, InKeyEvent); } } return Reply; } FReply SEditableText::OnKeyUp( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { return EditableTextLayout->HandleKeyUp(InKeyEvent); } FReply SEditableText::OnMouseButtonDown( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { return EditableTextLayout->HandleMouseButtonDown(InMyGeometry, InMouseEvent); } FReply SEditableText::OnMouseButtonUp( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { return EditableTextLayout->HandleMouseButtonUp(InMyGeometry, InMouseEvent); } FReply SEditableText::OnMouseMove( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { return EditableTextLayout->HandleMouseMove(InMyGeometry, InMouseEvent); } FReply SEditableText::OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) { return EditableTextLayout->HandleMouseButtonDoubleClick(InMyGeometry, InMouseEvent); } FCursorReply SEditableText::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const { return FCursorReply::Cursor( EMouseCursor::TextEditBeam ); } const FSlateBrush* SEditableText::GetFocusBrush() const { return nullptr; } bool SEditableText::IsInteractable() const { return IsEnabled(); } bool SEditableText::ComputeVolatility() const { return SWidget::ComputeVolatility() || HasKeyboardFocus() || EditableTextLayout->ComputeVolatility() || Font.IsBound() || ColorAndOpacity.IsBound() || BackgroundImageSelected.IsBound() || bIsReadOnly.IsBound() || bIsPassword.IsBound() || MinDesiredWidth.IsBound(); } void SEditableText::SetHintText( const TAttribute< FText >& InHintText ) { EditableTextLayout->SetHintText(InHintText); } FText SEditableText::GetHintText() const { return EditableTextLayout->GetHintText(); } void SEditableText::SetSearchText(const TAttribute& InSearchText) { EditableTextLayout->SetSearchText(InSearchText); } FText SEditableText::GetSearchText() const { return EditableTextLayout->GetSearchText(); } void SEditableText::SetIsReadOnly( TAttribute< bool > InIsReadOnly ) { bIsReadOnly = InIsReadOnly; } void SEditableText::SetIsPassword( TAttribute< bool > InIsPassword ) { bIsPassword = InIsPassword; PlainTextMarshaller->SetIsPassword(bIsPassword); } void SEditableText::SetColorAndOpacity(TAttribute Color) { ColorAndOpacity = Color; } void SEditableText::SetMinDesiredWidth(const TAttribute& InMinDesiredWidth) { MinDesiredWidth = InMinDesiredWidth; } void SEditableText::SetIsCaretMovedWhenGainFocus(const TAttribute& InIsCaretMovedWhenGainFocus) { bIsCaretMovedWhenGainFocus = InIsCaretMovedWhenGainFocus; } void SEditableText::SetSelectAllTextWhenFocused(const TAttribute& InSelectAllTextWhenFocused) { bSelectAllTextWhenFocused = InSelectAllTextWhenFocused; } void SEditableText::SetRevertTextOnEscape(const TAttribute& InRevertTextOnEscape) { bRevertTextOnEscape = InRevertTextOnEscape; } void SEditableText::SetClearKeyboardFocusOnCommit(const TAttribute& InClearKeyboardFocusOnCommit) { bClearKeyboardFocusOnCommit = InClearKeyboardFocusOnCommit; } void SEditableText::SetSelectAllTextOnCommit(const TAttribute& InSelectAllTextOnCommit) { bSelectAllTextOnCommit = InSelectAllTextOnCommit; } void SEditableText::SetSelectWordOnMouseDoubleClick(const TAttribute& InSelectWordOnMouseDoubleClick) { bSelectWordOnMouseDoubleClick = InSelectWordOnMouseDoubleClick; } void SEditableText::SetJustification(const TAttribute& InJustification) { EditableTextLayout->SetJustification(InJustification); } void SEditableText::SetAllowContextMenu(const TAttribute< bool >& InAllowContextMenu) { bAllowContextMenu = InAllowContextMenu; } void SEditableText::SetEnableIntegratedKeyboard(const TAttribute& InEnableIntegratedKeyboard) { bEnableIntegratedKeyboard = InEnableIntegratedKeyboard; } void SEditableText::SetVirtualKeyboardDismissAction(TAttribute< EVirtualKeyboardDismissAction > InVirtualKeyboardDismissAction) { VirtualKeyboardDismissAction = InVirtualKeyboardDismissAction; } void SEditableText::SetTextShapingMethod(const TOptional& InTextShapingMethod) { EditableTextLayout->SetTextShapingMethod(InTextShapingMethod); } void SEditableText::SetTextFlowDirection(const TOptional& InTextFlowDirection) { EditableTextLayout->SetTextFlowDirection(InTextFlowDirection); } void SEditableText::SetOverflowPolicy(TOptional InOverflowPolicy) { EditableTextLayout->SetOverflowPolicy(InOverflowPolicy); } bool SEditableText::AnyTextSelected() const { return EditableTextLayout->AnyTextSelected(); } void SEditableText::SelectAllText() { EditableTextLayout->SelectAllText(); } void SEditableText::ClearSelection() { EditableTextLayout->ClearSelection(); } FText SEditableText::GetSelectedText() const { return EditableTextLayout->GetSelectedText(); } void SEditableText::GoTo(const FTextLocation& NewLocation) { EditableTextLayout->GoTo(NewLocation); } void SEditableText::GoTo(const ETextLocation NewLocation) { EditableTextLayout->GoTo(NewLocation); } void SEditableText::ScrollTo(const FTextLocation& NewLocation) { EditableTextLayout->ScrollTo(NewLocation); } void SEditableText::ScrollTo(const ETextLocation NewLocation) { EditableTextLayout->ScrollTo(NewLocation); } void SEditableText::BeginSearch(const FText& InSearchText, const ESearchCase::Type InSearchCase, const bool InReverse) { EditableTextLayout->BeginSearch(InSearchText, InSearchCase, InReverse); } void SEditableText::AdvanceSearch(const bool InReverse) { EditableTextLayout->AdvanceSearch(InReverse); } void SEditableText::EnableTextInputMethodContext() { EditableTextLayout->EnableTextInputMethodContext(); } FTextSelection SEditableText::GetSelection() const { return EditableTextLayout->GetSelection(); } void SEditableText::SelectText(const FTextLocation& InSelectionStart, const FTextLocation& InCursorLocation) { EditableTextLayout->SelectText(InSelectionStart, InCursorLocation); } void SEditableText::SynchronizeTextStyle() { // Has the style used for this editable text changed? bool bTextStyleChanged = false; FTextBlockStyle NewTextStyle = EditableTextLayout->GetTextStyle(); // Sync from the font override if (Font.IsSet()) { const FSlateFontInfo& NewFontInfo = Font.Get(); if (!NewTextStyle.Font.IsIdenticalTo(NewFontInfo)) { NewTextStyle.Font = NewFontInfo; bTextStyleChanged = true; } } // Sync from the color override if (ColorAndOpacity.IsSet()) { const FSlateColor& NewColorAndOpacity = ColorAndOpacity.Get(); if (NewTextStyle.ColorAndOpacity != NewColorAndOpacity) { NewTextStyle.ColorAndOpacity = NewColorAndOpacity; bTextStyleChanged = true; } } // Sync from the highlight shape override if (BackgroundImageSelected.IsSet()) { const FSlateBrush* NewSelectionBrush = BackgroundImageSelected.Get(); if (NewSelectionBrush && NewTextStyle.HighlightShape != *NewSelectionBrush) { NewTextStyle.HighlightShape = *NewSelectionBrush; bTextStyleChanged = true; } } if (bTextStyleChanged) { EditableTextLayout->SetTextStyle(NewTextStyle); EditableTextLayout->ForceRefreshTextLayout(EditableTextLayout->GetEditableText()); } } bool SEditableText::IsTextReadOnly() const { return bIsReadOnly.Get(false); } bool SEditableText::IsTextPassword() const { return bIsPassword.Get(false); } bool SEditableText::IsMultiLineTextEdit() const { return false; } bool SEditableText::IsIntegratedKeyboardEnabled() const { return bEnableIntegratedKeyboard.Get(false); } bool SEditableText::ShouldJumpCursorToEndWhenFocused() const { return bIsCaretMovedWhenGainFocus.Get(false); } bool SEditableText::ShouldSelectAllTextWhenFocused() const { return bSelectAllTextWhenFocused.Get(false); } bool SEditableText::ShouldClearTextSelectionOnFocusLoss() const { return true; } bool SEditableText::ShouldRevertTextOnEscape() const { return bRevertTextOnEscape.Get(false); } bool SEditableText::ShouldClearKeyboardFocusOnCommit() const { return bClearKeyboardFocusOnCommit.Get(false); } bool SEditableText::ShouldSelectAllTextOnCommit() const { return bSelectAllTextOnCommit.Get(false); } bool SEditableText::ShouldSelectWordOnMouseDoubleClick() const { return bSelectWordOnMouseDoubleClick.Get(true); } bool SEditableText::CanInsertCarriageReturn() const { return false; } bool SEditableText::CanTypeCharacter(const TCHAR InChar) const { if (OnIsTypedCharValid.IsBound()) { return OnIsTypedCharValid.Execute(InChar); } return InChar != TEXT('\t'); } void SEditableText::EnsureActiveTick() { TSharedPtr ActiveTickTimerPin = ActiveTickTimer.Pin(); if (ActiveTickTimerPin.IsValid()) { return; } auto DoActiveTick = [this](double InCurrentTime, float InDeltaTime) -> EActiveTimerReturnType { // Continue if we still have focus, otherwise treat as a fire-and-forget Tick() request const bool bShouldAppearFocused = HasKeyboardFocus() || EditableTextLayout->HasActiveContextMenu(); return (bShouldAppearFocused) ? EActiveTimerReturnType::Continue : EActiveTimerReturnType::Stop; }; const float TickPeriod = EditableTextDefs::BlinksPerSecond * 0.5f; ActiveTickTimer = RegisterActiveTimer(TickPeriod, FWidgetActiveTimerDelegate::CreateLambda(DoActiveTick)); } EKeyboardType SEditableText::GetVirtualKeyboardType() const { return VirtualKeyboardType.Get(); } FVirtualKeyboardOptions SEditableText::GetVirtualKeyboardOptions() const { return VirtualKeyboardOptions; } EVirtualKeyboardTrigger SEditableText::GetVirtualKeyboardTrigger() const { return VirtualKeyboardTrigger.Get(); } EVirtualKeyboardDismissAction SEditableText::GetVirtualKeyboardDismissAction() const { return VirtualKeyboardDismissAction.Get(); } TSharedRef SEditableText::GetSlateWidget() { return AsShared(); } TSharedPtr SEditableText::GetSlateWidgetPtr() { if (DoesSharedInstanceExist()) { return AsShared(); } return nullptr; } TSharedPtr SEditableText::BuildContextMenuContent() const { if (!bAllowContextMenu.Get()) { return nullptr; } if (OnContextMenuOpening.IsBound()) { return OnContextMenuOpening.Execute(); } return EditableTextLayout->BuildDefaultContextMenu(MenuExtender); } void SEditableText::OnTextChanged(const FText& InText) { OnTextChangedCallback.ExecuteIfBound(InText); } void SEditableText::OnTextCommitted(const FText& InText, const ETextCommit::Type InTextAction) { OnTextCommittedCallback.ExecuteIfBound(InText, InTextAction); } void SEditableText::OnCursorMoved(const FTextLocation& InLocation) { Invalidate(EInvalidateWidgetReason::Layout); } float SEditableText::UpdateAndClampHorizontalScrollBar(const float InViewOffset, const float InViewFraction, const EVisibility InVisiblityOverride) { return EditableTextLayout->GetScrollOffset().X; } float SEditableText::UpdateAndClampVerticalScrollBar(const float InViewOffset, const float InViewFraction, const EVisibility InVisiblityOverride) { return 0.0f; } #if WITH_ACCESSIBILITY TSharedRef SEditableText::CreateAccessibleWidget() { return MakeShareable(new FSlateAccessibleEditableText(SharedThis(this))); } TOptional SEditableText::GetDefaultAccessibleText(EAccessibleType AccessibleType) const { return GetHintText(); } #endif