void UJavascriptMultiLineEditableTextBox::GoTo(int32 Line, int32 Offset) { if (MyEditableTextBlock.IsValid()) { MyEditableTextBlock->GoTo(FTextLocation(Line, Offset)); } }
SMultiLineEditableText::SMultiLineEditableText() : CursorPosition( FTextLocation() ) , FontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 10 ) , NumLinesScrollOffset( 0.0f ) , PreferredCursorOffsetInLine( 0 ) , LastCursorInteractionTime( 0 ) , PreferredSize( FVector2D::ZeroVector ) { }
SMultiLineEditableText::FTextLocation SMultiLineEditableText::TranslatedLocation( const FTextLocation& Location, int8 Direction ) const { const int32 OffsetInLine = Location.GetOffset() + Direction; if ( OffsetInLine > TextLines[Location.GetLineIndex()]->Len() && Location.GetLineIndex() < TextLines.Num()-1 ) { // We're going over the end of the line and we aren't on the last line return FTextLocation( Location.GetLineIndex() + 1, 0 ); } else if ( OffsetInLine < 0 && Location.GetLineIndex() > 0 ) { // We're stepping before the beginning of the line, and we're not on the first line. return FTextLocation( Location.GetLineIndex() - 1, TextLines[Location.GetLineIndex() - 1]->Len() ); } else { return FTextLocation( Location.GetLineIndex(), FMath::Clamp(OffsetInLine, 0, TextLines[Location.GetLineIndex()]->Len()) ); } }
SMultiLineEditableText::FTextLocation SMultiLineEditableText::TranslateLocationVertical( const FTextLocation& Location, int8 Direction ) const { const int32 CurrentWrappedLineIndex = WrappedText.FindMatch( FMatchWrappedLine( TextLines[Location.GetLineIndex()].Get(), Location.GetOffset() ) ); const FWrappedStringSlice& CurrentWrappedLine = WrappedText[CurrentWrappedLineIndex]; ensure(CurrentWrappedLineIndex != INDEX_NONE); const int32 NewWrappedLinedIndex = FMath::Clamp(CurrentWrappedLineIndex + Direction, 0, WrappedText.Num()-1); const FWrappedStringSlice& NewWrappedLine = WrappedText[NewWrappedLinedIndex]; // We moved wrapped lines. Did we actually move to a different source line? const bool bMovedToNewSourceLine = &CurrentWrappedLine.SourceString != &NewWrappedLine.SourceString; const int32 NewSourceLineIndex = Location.GetLineIndex() + ( bMovedToNewSourceLine ? Direction : 0 ); // Our horizontal position is the clamped version of whatever the user explicitly set with horizontal movement. const int32 NewOffsetInLine = FMath::Clamp(NewWrappedLine.FirstCharIndex + PreferredCursorOffsetInLine, 0, NewWrappedLine.LastCharIndex); return FTextLocation( NewSourceLineIndex, NewOffsetInLine ); }
SMultiLineEditableText::FTextLocation SMultiLineEditableText::AbsoluteToWrapped( const FTextLocation& Location ) const { const int32 CurrentWrappedLineIndex = WrappedText.FindMatch( FMatchWrappedLine( TextLines[Location.GetLineIndex()].Get(), Location.GetOffset() ) ); const int32 OffsetInWrappedLine = Location.GetOffset() - WrappedText[CurrentWrappedLineIndex].FirstCharIndex; return FTextLocation(CurrentWrappedLineIndex, OffsetInWrappedLine); }
int32 SMultiLineEditableText::OnPaint( const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { const int32 TextLayer = LayerId; const int32 SelectionLayer = LayerId + 1; const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService(); const float CursorWidth = 2.0f; const double CurrentTime = FSlateApplication::Get().GetCurrentTime(); const bool bEnabled = ShouldBeEnabled( bParentEnabled ); const ESlateDrawEffect::Type DrawEffects = (bEnabled) ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect; const int32 WholeLinesOffset = FMath::Floor( NumLinesScrollOffset ); const float PartialLineOffset = FMath::Fractional( NumLinesScrollOffset ); if (WrappedText.Num() > 0) { float HeightOffsetSoFar = - PartialLineOffset * WrappedText[FMath::Clamp(WholeLinesOffset, 0, WrappedText.Num()-1)].Size.Y; bool FilledVerticalSpace = false; const FTextRange Selection = FTextRange(SelectionStart.GetValue(), CursorPosition); FTextRange SelectionInWrappedText = (SelectionStart.IsSet()) ? AbsoluteToWrapped( Selection ) : FTextRange( FTextLocation(), FTextLocation() ); bool bInSelection = SelectionInWrappedText.First.GetLineIndex() < WholeLinesOffset && SelectionInWrappedText.First.GetLineIndex() > WholeLinesOffset; for ( int32 LineIndex=WholeLinesOffset; !FilledVerticalSpace && LineIndex < WrappedText.Num(); ++LineIndex ) { const FWrappedStringSlice& ThisWrappedLine = WrappedText[LineIndex]; // @todo FontService: Copy is undesirable? const FString StringToPaint = ThisWrappedLine.SourceString.Mid( ThisWrappedLine.FirstCharIndex, ThisWrappedLine.LastCharIndex-ThisWrappedLine.FirstCharIndex ); // Draw this wrapped line of text FSlateDrawElement::MakeText( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(FVector2D(0, HeightOffsetSoFar), FVector2D::UnitVector), StringToPaint, FontInfo, MyClippingRect, ShouldBeEnabled(bParentEnabled), InWidgetStyle.GetColorAndOpacityTint() ); static const FLinearColor SelectionColor = FLinearColor(0,0,1.0f,0.25f); // DRAW SELECTION if ( SelectionStart.IsSet() ) { const bool bIsFirstSelectionLine = (&(TextLines[Selection.First.GetLineIndex()].Get()) == &ThisWrappedLine.SourceString) && Selection.First.GetOffset() >= ThisWrappedLine.FirstCharIndex && Selection.First.GetOffset() <= ThisWrappedLine.LastCharIndex; const bool bIsLastSelectionLine = (&(TextLines[Selection.Last.GetLineIndex()].Get()) == &ThisWrappedLine.SourceString) && Selection.Last.GetOffset() >= ThisWrappedLine.FirstCharIndex && Selection.Last.GetOffset() <= ThisWrappedLine.LastCharIndex; if ( bIsFirstSelectionLine && bIsLastSelectionLine ) { // Selection contained within a single line. const FString StringBeforeSelection = ThisWrappedLine.SourceString.Left( SelectionInWrappedText.First.GetOffset() ); const FVector2D SizeBeforeSelection = FontMeasureService->Measure(StringBeforeSelection, FontInfo, AllottedGeometry.Scale); const FString SelectedString = ThisWrappedLine.SourceString.Mid( SelectionInWrappedText.First.GetOffset(), SelectionInWrappedText.Last.GetOffset() - SelectionInWrappedText.First.GetOffset() ); const FVector2D SelectionSize = FontMeasureService->Measure(SelectedString, FontInfo, AllottedGeometry.Scale); FSlateDrawElement::MakeBox( OutDrawElements, SelectionLayer, AllottedGeometry.ToPaintGeometry(FVector2D(SizeBeforeSelection.X, HeightOffsetSoFar), SelectionSize), FCoreStyle::Get().GetBrush( "WhiteBrush" ), MyClippingRect, DrawEffects, SelectionColor ); } else if ( bIsFirstSelectionLine ) { // Selection starts on this line, ends on another. bInSelection = true; // @todo FontService: Copy is unnecessary const FString StringBeforeSelection = StringToPaint.Left(SelectionInWrappedText.First.GetOffset()); const FVector2D UnselectedPortionSize = FontMeasureService->Measure(StringBeforeSelection, FontInfo, AllottedGeometry.Scale); FSlateDrawElement::MakeBox( OutDrawElements, SelectionLayer, AllottedGeometry.ToPaintGeometry(FVector2D(UnselectedPortionSize.X, HeightOffsetSoFar), ThisWrappedLine.Size - FVector2D(UnselectedPortionSize.X, 0)), FCoreStyle::Get().GetBrush( "WhiteBrush" ), MyClippingRect, DrawEffects, SelectionColor ); } else if ( bIsLastSelectionLine ) { // Selection starts on some line above, ends on this one. bInSelection = false; // @todo FontService: Copy is unnecessary const FString StringBeforeSelection = StringToPaint.Left(SelectionInWrappedText.Last.GetOffset()); const FVector2D SelectedPortionSize = FontMeasureService->Measure(StringBeforeSelection, FontInfo, AllottedGeometry.Scale); FSlateDrawElement::MakeBox( OutDrawElements, SelectionLayer, AllottedGeometry.ToPaintGeometry(FVector2D(0, HeightOffsetSoFar), FVector2D(SelectedPortionSize.X, ThisWrappedLine.Size.Y)), FCoreStyle::Get().GetBrush( "WhiteBrush" ), MyClippingRect, DrawEffects, SelectionColor ); } else if (bInSelection) { // The entirety of this line is selected FSlateDrawElement::MakeBox( OutDrawElements, SelectionLayer, AllottedGeometry.ToPaintGeometry(FVector2D(0, HeightOffsetSoFar), ThisWrappedLine.Size), FCoreStyle::Get().GetBrush( "WhiteBrush" ), MyClippingRect, DrawEffects, SelectionColor ); } } // DRAW CURSOR if (this->HasKeyboardFocus()) { const bool CursorIsOnThisTextLine = ( &ThisWrappedLine.SourceString == &(TextLines[CursorPosition.GetLineIndex()].Get()) ); const bool bIsStartOfNewLine = ThisWrappedLine.FirstCharIndex == 0; if ( CursorIsOnThisTextLine && ((bIsStartOfNewLine && CursorPosition.GetOffset() == 0) || CursorPosition.GetOffset() > ThisWrappedLine.FirstCharIndex) && CursorPosition.GetOffset() <= ThisWrappedLine.LastCharIndex ) { // The cursor is always visible (i.e. not blinking) when we're interacting with it; otherwise it might get lost. const bool bForceCursorVisible = (CurrentTime - LastCursorInteractionTime) < EditableTextDefs::CaretBlinkPauseTime; const float CursorOpacity = (bForceCursorVisible) ? 1.0f : FMath::Round( FMath::MakePulsatingValue( CurrentTime, EditableTextDefs::BlinksPerSecond )); const int32 CursorOffsetInWrappedLine = CursorPosition.GetOffset() - ThisWrappedLine.FirstCharIndex; const FVector2D SlateUnitsCursorOffsetInLine = FontMeasureService->Measure( StringToPaint.Left(CursorOffsetInWrappedLine), FontInfo, AllottedGeometry.Scale ); const FVector2D DrawPosition = FVector2D( SlateUnitsCursorOffsetInLine.X, HeightOffsetSoFar ); const FVector2D DrawSize = FVector2D( CursorWidth, ThisWrappedLine.Size.Y ); // @todo: State Styles - make this brush part of the widget style const FSlateBrush* StyleInfo = FCoreStyle::Get().GetBrush("EditableText.SelectionBackground"); FSlateDrawElement::MakeBox( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(DrawPosition, DrawSize), StyleInfo, MyClippingRect, DrawEffects, FLinearColor(1,1,1,CursorOpacity) ); } } HeightOffsetSoFar += WrappedText[LineIndex].Size.Y; } } return SelectionLayer; }
void SMultiLineEditableText::SelectAllText() { SelectionStart = FTextLocation(0,0); CursorPosition = FTextLocation( TextLines.Num() - 1, TextLines[TextLines.Num()-1]->Len() ); }