333 lines
11 KiB
C++
333 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Widgets/Input/SVirtualKeyboardEntry.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Text/TextEditHelper.h"
|
|
|
|
/** Constructor */
|
|
SVirtualKeyboardEntry::SVirtualKeyboardEntry()
|
|
: ScrollHelper(),
|
|
bWasFocusedByLastMouseDown( false ),
|
|
bIsChangingText( false )
|
|
{ }
|
|
|
|
|
|
/**
|
|
* Construct this widget
|
|
*
|
|
* @param InArgs The declaration data for this widget
|
|
*/
|
|
void SVirtualKeyboardEntry::Construct( const FArguments& InArgs )
|
|
{
|
|
Text = InArgs._Text;
|
|
HintText = InArgs._HintText;
|
|
|
|
Font = InArgs._Font;
|
|
ColorAndOpacity = InArgs._ColorAndOpacity;
|
|
IsReadOnly = InArgs._IsReadOnly;
|
|
ClearKeyboardFocusOnCommit = InArgs._ClearKeyboardFocusOnCommit;
|
|
OnTextChanged = InArgs._OnTextChanged;
|
|
OnTextCommitted = InArgs._OnTextCommitted;
|
|
MinDesiredWidth = InArgs._MinDesiredWidth;
|
|
KeyboardType = InArgs._KeyboardType;
|
|
VirtualKeyboardOptions = InArgs._VirtualKeyboardOptions;
|
|
bEnableIntegratedKeyboard = InArgs._EnableIntegratedKeyboard;
|
|
}
|
|
|
|
void SVirtualKeyboardEntry::SetText(const TAttribute< FText >& InNewText)
|
|
{
|
|
EditedText = InNewText.Get();
|
|
|
|
// Don't set text if the text attribute has a 'getter' binding on it, otherwise we'd blow away
|
|
// that binding. If there is a getter binding, then we'll assume it will provide us with
|
|
// updated text after we've fired our 'text changed' callbacks
|
|
if (!Text.IsBound())
|
|
{
|
|
Text.Set(EditedText);
|
|
}
|
|
|
|
bNeedsUpdate = true;
|
|
}
|
|
|
|
void SVirtualKeyboardEntry::SetTextFromVirtualKeyboard(const FText& InNewText, ETextEntryType TextEntryType)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
// Only set the text if the text attribute doesn't have a getter binding (otherwise it would be blown away).
|
|
// If it is bound, we'll assume that OnTextChanged will handle the update.
|
|
if (!Text.IsBound())
|
|
{
|
|
Text.Set(InNewText);
|
|
}
|
|
|
|
if (!InNewText.EqualTo(EditedText))
|
|
{
|
|
EditedText = InNewText;
|
|
|
|
// This method is called from the main thread (i.e. not the game thread) of the device with the virtual keyboard
|
|
// This causes the app to crash on those devices, so we're using polling here to ensure delegates are
|
|
// fired on the game thread in Tick.
|
|
bNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
void SVirtualKeyboardEntry::SetSelectionFromVirtualKeyboard(int InSelStart, int InSelEnd)
|
|
{
|
|
check(IsInGameThread());
|
|
//Nothing to do for widgets without a cursor
|
|
}
|
|
|
|
/**
|
|
* Restores the text to the original state
|
|
*/
|
|
void SVirtualKeyboardEntry::RestoreOriginalText()
|
|
{
|
|
if( HasTextChangedFromOriginal() )
|
|
{
|
|
SetTextFromVirtualKeyboard(OriginalText, ETextEntryType::TextEntryCanceled);
|
|
}
|
|
}
|
|
|
|
bool SVirtualKeyboardEntry::HasTextChangedFromOriginal() const
|
|
{
|
|
return ( !IsReadOnly.Get() && !EditedText.EqualTo(OriginalText) );
|
|
}
|
|
|
|
/**
|
|
* Sets the font used to draw the text
|
|
*
|
|
* @param InNewFont The new font to use
|
|
*/
|
|
void SVirtualKeyboardEntry::SetFont( const TAttribute< FSlateFontInfo >& InNewFont )
|
|
{
|
|
Font = InNewFont;
|
|
}
|
|
|
|
/**
|
|
* Checks to see if this widget supports keyboard focus. Override this in derived classes.
|
|
*
|
|
* @return True if this widget can take keyboard focus
|
|
*/
|
|
bool SVirtualKeyboardEntry::SupportsKeyboardFocus() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool SVirtualKeyboardEntry::GetIsReadOnly() const
|
|
{
|
|
return IsReadOnly.Get();
|
|
}
|
|
|
|
/**
|
|
* Ticks this widget. Override in derived classes, but always call the parent implementation.
|
|
*
|
|
* @param AllottedGeometry Space allotted to this widget
|
|
* @param InCurrentTime Current absolute real time
|
|
* @param InDeltaTime Real time passed since last tick
|
|
*/
|
|
void SVirtualKeyboardEntry::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
if(bNeedsUpdate)
|
|
{
|
|
// Let outsiders know that the text content has been changed
|
|
OnTextChanged.ExecuteIfBound( HasKeyboardFocus() ? EditedText : Text.Get() );
|
|
bNeedsUpdate = false;
|
|
}
|
|
|
|
|
|
// We'll draw with the 'focused' look if we're either focused or we have a context menu summoned
|
|
const bool bShouldAppearFocused = HasKeyboardFocus();
|
|
}
|
|
|
|
/**
|
|
* Computes the desired size of this widget (SWidget)
|
|
*
|
|
* @return The widget's desired size
|
|
*/
|
|
FVector2D SVirtualKeyboardEntry::ComputeDesiredSize( float ) const
|
|
{
|
|
const FSlateFontInfo& FontInfo = Font.Get();
|
|
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
|
|
const float FontMaxCharHeight = FTextEditHelper::GetFontHeight(FontInfo);
|
|
|
|
FVector2D TextSize;
|
|
|
|
const FString StringToRender = GetStringToRender();
|
|
if( !StringToRender.IsEmpty() )
|
|
{
|
|
TextSize = FontMeasureService->Measure( StringToRender, FontInfo );
|
|
}
|
|
else
|
|
{
|
|
TextSize = FontMeasureService->Measure( HintText.Get().ToString(), FontInfo );
|
|
}
|
|
|
|
return FVector2D( FMath::Max(TextSize.X, MinDesiredWidth.Get()), FMath::Max(TextSize.Y, FontMaxCharHeight) );
|
|
}
|
|
|
|
int32 SVirtualKeyboardEntry::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
|
|
{
|
|
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
|
|
// The text and some effects draws in front of the widget's background and selection.
|
|
const int32 SelectionLayer = 0;
|
|
const int32 TextLayer = 1;
|
|
|
|
// See if a disabled effect should be used
|
|
bool bEnabled = ShouldBeEnabled( bParentEnabled );
|
|
bool bIsReadonly = IsReadOnly.Get();
|
|
ESlateDrawEffect DrawEffects = (bEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
|
|
|
|
const FSlateFontInfo& FontInfo = Font.Get();
|
|
const FString VisibleText = GetStringToRender();
|
|
const FLinearColor ThisColorAndOpacity = ColorAndOpacity.Get().GetColor(InWidgetStyle);
|
|
const FLinearColor ColorAndOpacitySRGB = ThisColorAndOpacity * InWidgetStyle.GetColorAndOpacityTint();
|
|
const float FontMaxCharHeight = FTextEditHelper::GetFontHeight(FontInfo);
|
|
const double CurrentTime = FSlateApplication::Get().GetCurrentTime();
|
|
|
|
// We'll draw with the 'focused' look if we're either focused or we have a context menu summoned
|
|
const bool bShouldAppearFocused = HasKeyboardFocus();
|
|
|
|
const float DrawPositionY = ( AllottedGeometry.GetLocalSize().Y / 2 ) - ( FontMaxCharHeight / 2 );
|
|
|
|
if (VisibleText.Len() == 0)
|
|
{
|
|
// Draw the hint text.
|
|
const FLinearColor HintTextColor = FLinearColor(ColorAndOpacitySRGB.R, ColorAndOpacitySRGB.G, ColorAndOpacitySRGB.B, 0.35f);
|
|
const FString ThisHintText = this->HintText.Get().ToString();
|
|
FSlateDrawElement::MakeText(
|
|
OutDrawElements,
|
|
LayerId + TextLayer,
|
|
AllottedGeometry.ToPaintGeometry( AllottedGeometry.GetLocalSize(), FSlateLayoutTransform(FVector2f( 0.0, DrawPositionY )) ),
|
|
ThisHintText, // Text
|
|
FontInfo, // Font information (font name, size)
|
|
DrawEffects, // Effects to use
|
|
HintTextColor // Color
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Draw the text
|
|
#if 0
|
|
// NOTE: this code is causing the text to not be visible at all once entered on device.
|
|
// I've replaced it with the (working) hint-text version for now (without scrolling support)
|
|
// until a real fix can be found. -dnikdel
|
|
|
|
// Only draw the text that's potentially visible
|
|
const float ScrollAreaLeft = ScrollHelper.ToScrollerSpace( FVector2D::ZeroVector ).X;
|
|
const float ScrollAreaRight = ScrollHelper.ToScrollerSpace( AllottedGeometry.Size ).X;
|
|
const int32 FirstVisibleCharacter = FontMeasureService->FindCharacterIndexAtOffset( VisibleText, FontInfo, ScrollAreaLeft );
|
|
const int32 LastVisibleCharacter = FontMeasureService->FindCharacterIndexAtOffset( VisibleText, FontInfo, ScrollAreaRight );
|
|
const FString PotentiallyVisibleText( VisibleText.Mid( FirstVisibleCharacter, ( LastVisibleCharacter - FirstVisibleCharacter ) + 1 ) );
|
|
const float FirstVisibleCharacterPosition = FontMeasureService->Measure( VisibleText.Left( FirstVisibleCharacter ), FontInfo ).X;
|
|
|
|
const float TextVertOffset = FontMaxCharHeight;
|
|
const FVector2D DrawPosition = ScrollHelper.FromScrollerSpace( FVector2D( FirstVisibleCharacterPosition, DrawPositionY + TextVertOffset ) );
|
|
const FVector2D DrawSize = ScrollHelper.SizeFromScrollerSpace( AllottedGeometry.Size );
|
|
|
|
FSlateDrawElement::MakeText(
|
|
OutDrawElements,
|
|
LayerId + TextLayer,
|
|
AllottedGeometry.ToPaintGeometry( DrawPosition, DrawSize ),
|
|
PotentiallyVisibleText, // Text
|
|
FontInfo, // Font information (font name, size)
|
|
DrawEffects, // Effects to use
|
|
ColorAndOpacitySRGB // Color
|
|
);
|
|
#else
|
|
// Draw the whole text for now
|
|
FSlateDrawElement::MakeText(
|
|
OutDrawElements,
|
|
LayerId + TextLayer,
|
|
AllottedGeometry.ToPaintGeometry(AllottedGeometry.GetLocalSize(), FSlateLayoutTransform(FVector2f(0, DrawPositionY))),
|
|
VisibleText, // Text
|
|
FontInfo, // Font information (font name, size)
|
|
DrawEffects, // Effects to use
|
|
ColorAndOpacitySRGB // Color
|
|
);
|
|
#endif
|
|
}
|
|
|
|
return LayerId + TextLayer;
|
|
}
|
|
|
|
FReply SVirtualKeyboardEntry::OnFocusReceived( const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent )
|
|
{
|
|
// The user wants to edit text. Make a copy of the observed text for the user to edit.
|
|
EditedText = Text.Get();
|
|
|
|
int32 CaretPosition = EditedText.ToString().Len();
|
|
FSlateApplication& CurrentApp = FSlateApplication::Get();
|
|
CurrentApp.ShowVirtualKeyboard(true, InFocusEvent.GetUser(), SharedThis(this));
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
/**
|
|
* Called when this widget loses the keyboard focus. This event does not bubble.
|
|
*
|
|
* @param InFocusEvent FocusEvent
|
|
*/
|
|
void SVirtualKeyboardEntry::OnFocusLost( const FFocusEvent& InFocusEvent )
|
|
{
|
|
// See if user explicitly tabbed away or moved focus
|
|
ETextCommit::Type TextAction;
|
|
switch ( InFocusEvent.GetCause() )
|
|
{
|
|
case EFocusCause::Navigation:
|
|
case EFocusCause::Mouse:
|
|
TextAction = ETextCommit::OnUserMovedFocus;
|
|
break;
|
|
|
|
case EFocusCause::Cleared:
|
|
TextAction = ETextCommit::OnCleared;
|
|
break;
|
|
|
|
default:
|
|
TextAction = ETextCommit::Default;
|
|
break;
|
|
}
|
|
|
|
FSlateApplication& CurrentApp = FSlateApplication::Get();
|
|
CurrentApp.ShowVirtualKeyboard(false, InFocusEvent.GetUser());
|
|
|
|
OnTextCommitted.ExecuteIfBound( EditedText, TextAction );
|
|
}
|
|
|
|
/**
|
|
* Gets the text that needs to be rendered
|
|
*
|
|
* @return Text string
|
|
*/
|
|
FString SVirtualKeyboardEntry::GetStringToRender() const
|
|
{
|
|
FString VisibleText;
|
|
|
|
if(HasKeyboardFocus())
|
|
{
|
|
VisibleText = EditedText.ToString();
|
|
}
|
|
else
|
|
{
|
|
VisibleText = Text.Get().ToString();
|
|
}
|
|
|
|
// If this string is a password we need to replace all the characters in the string with something else
|
|
if ( KeyboardType.Get() == EKeyboardType::Keyboard_Password )
|
|
{
|
|
const TCHAR Dot = 0x25CF;
|
|
int32 VisibleTextLen = VisibleText.Len();
|
|
VisibleText.Empty();
|
|
while ( VisibleTextLen )
|
|
{
|
|
VisibleText += Dot;
|
|
VisibleTextLen--;
|
|
}
|
|
}
|
|
|
|
return VisibleText;
|
|
}
|