Merge branch 'line-splitting'

This commit is contained in:
Steve Streeting 2023-04-20 16:38:11 +01:00
commit d102c5de67
2 changed files with 106 additions and 9 deletions

View File

@ -69,13 +69,19 @@ FText UTypewriterTextWidget::GetText() const
} }
void UTypewriterTextWidget::PlayLine(const FText& InLine, float Speed) void UTypewriterTextWidget::PlayLine(const FText& InLine, float Speed)
{
CurrentLine = InLine;
RemainingLinePart = CurrentLine.ToString();
PlayNextLinePart(Speed);
}
void UTypewriterTextWidget::PlayNextLinePart(float Speed)
{ {
check(GetWorld()); check(GetWorld());
FTimerManager& TimerManager = GetWorld()->GetTimerManager(); FTimerManager& TimerManager = GetWorld()->GetTimerManager();
TimerManager.ClearTimer(LetterTimer); TimerManager.ClearTimer(LetterTimer);
CurrentLine = InLine;
CurrentRunName = ""; CurrentRunName = "";
CurrentLetterIndex = 0; CurrentLetterIndex = 0;
CachedLetterIndex = 0; CachedLetterIndex = 0;
@ -87,7 +93,7 @@ void UTypewriterTextWidget::PlayLine(const FText& InLine, float Speed)
Segments.Empty(); Segments.Empty();
CachedSegmentText.Empty(); CachedSegmentText.Empty();
if (CurrentLine.IsEmpty()) if (RemainingLinePart.IsEmpty())
{ {
if (IsValid(LineText)) if (IsValid(LineText))
{ {
@ -107,6 +113,7 @@ void UTypewriterTextWidget::PlayLine(const FText& InLine, float Speed)
LineText->SetText(FText::GetEmpty()); LineText->SetText(FText::GetEmpty());
} }
bHasMoreLineParts = false;
bHasFinishedPlaying = false; bHasFinishedPlaying = false;
if (bFirstPlayLine) if (bFirstPlayLine)
@ -127,7 +134,20 @@ void UTypewriterTextWidget::PlayLine(const FText& InLine, float Speed)
void UTypewriterTextWidget::StartPlayLine() void UTypewriterTextWidget::StartPlayLine()
{ {
CalculateWrappedString(); CalculateWrappedString(RemainingLinePart);
if (MaxNumberOfLines > 0 && NumberOfLines > MaxNumberOfLines)
{
int MaxLength = CalculateMaxLength();
int TerminatorIndex = FindLastTerminator(RemainingLinePart, MaxLength);
int Length = TerminatorIndex + 1;
const FString& FirstLinePart = RemainingLinePart.Left(Length);
CalculateWrappedString(FirstLinePart);
RemainingLinePart.RightChopInline(Length);
bHasMoreLineParts = true;
}
FTimerDelegate Delegate; FTimerDelegate Delegate;
Delegate.BindUObject(this, &ThisClass::PlayNextLetter); Delegate.BindUObject(this, &ThisClass::PlayNextLetter);
@ -214,7 +234,56 @@ bool UTypewriterTextWidget::IsSentenceTerminator(TCHAR Letter)
return Letter == '.' || Letter == '!' || Letter == '?'; return Letter == '.' || Letter == '!' || Letter == '?';
} }
void UTypewriterTextWidget::CalculateWrappedString() bool UTypewriterTextWidget::IsClauseTerminator(TCHAR Letter)
{
return Letter == ',' || Letter == ';';
}
int UTypewriterTextWidget::FindLastTerminator(const FString& CurrentLineString, int Count)
{
int TerminatorIndex = CurrentLineString.FindLastCharByPredicate(IsSentenceTerminator, Count);
if (TerminatorIndex != INDEX_NONE)
{
return TerminatorIndex;
}
TerminatorIndex = CurrentLineString.FindLastCharByPredicate(IsClauseTerminator, Count);
if (TerminatorIndex != INDEX_NONE)
{
return TerminatorIndex;
}
TerminatorIndex = CurrentLineString.FindLastCharByPredicate(FText::IsWhitespace, Count);
if (TerminatorIndex != INDEX_NONE)
{
return TerminatorIndex;
}
return (Count - 1);
}
int UTypewriterTextWidget::CalculateMaxLength()
{
int MaxLength = 0;
int CurrentNumberOfLines = 1;
for (int i = 0; i < Segments.Num(); i++)
{
const FTypewriterTextSegment& Segment = Segments[i];
MaxLength += Segment.Text.Len();
if (Segment.Text.Equals(FString(TEXT("\n"))))
{
CurrentNumberOfLines++;
if (MaxNumberOfLines > 0 && CurrentNumberOfLines > MaxNumberOfLines)
{
break;
}
}
}
return MaxLength;
}
void UTypewriterTextWidget::CalculateWrappedString(const FString& CurrentLineString)
{ {
// Rich Text views give you: // Rich Text views give you:
// - A blank block at the start for some reason // - A blank block at the start for some reason
@ -223,6 +292,9 @@ void UTypewriterTextWidget::CalculateWrappedString()
// - The newlines we add are the only newlines in the output so that's the number of lines // - The newlines we add are the only newlines in the output so that's the number of lines
// If we've got here, that means the text isn't empty so 1 line at least // If we've got here, that means the text isn't empty so 1 line at least
NumberOfLines = 1; NumberOfLines = 1;
MaxLetterIndex = 0;
CombinedTextHeight = 0;
Segments.Empty();
if (IsValid(LineText) && LineText->GetTextLayout().IsValid()) if (IsValid(LineText) && LineText->GetTextLayout().IsValid())
{ {
TSharedPtr<FSlateTextLayout> Layout = LineText->GetTextLayout(); TSharedPtr<FSlateTextLayout> Layout = LineText->GetTextLayout();
@ -233,7 +305,7 @@ void UTypewriterTextWidget::CalculateWrappedString()
Layout->ClearLines(); Layout->ClearLines();
Layout->SetWrappingWidth(TextBoxSize.X); Layout->SetWrappingWidth(TextBoxSize.X);
Marshaller->SetText(CurrentLine.ToString(), *Layout.Get()); Marshaller->SetText(CurrentLineString, *Layout.Get());
Layout->UpdateLayout(); Layout->UpdateLayout();
bool bHasWrittenText = false; bool bHasWrittenText = false;
@ -299,7 +371,7 @@ void UTypewriterTextWidget::CalculateWrappedString()
} }
else else
{ {
Segments.Add(FTypewriterTextSegment{CurrentLine.ToString()}); Segments.Add(FTypewriterTextSegment{CurrentLineString});
MaxLetterIndex = Segments[0].Text.Len(); MaxLetterIndex = Segments[0].Text.Len();
} }

View File

@ -84,6 +84,10 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Typewriter") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Typewriter")
float PauseTimeAtSentenceTerminators = 0.5f; float PauseTimeAtSentenceTerminators = 0.5f;
/// If set > 0, splits a single PlayLine into multiple segments of this number of lines maximum
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Typewriter")
int MaxNumberOfLines = 0;
/// Set Text immediately /// Set Text immediately
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void SetText(const FText& InText); void SetText(const FText& InText);
@ -92,16 +96,33 @@ public:
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
FText GetText() const; FText GetText() const;
/**
* Play a line of text.
* Note: if, when line splits are calculated, this line exceeds MaxNumberOfLines, then only this number of lines
* will be played by this call. In that case, HasMoreLineParts() will return true, and you will need to call
* PlayNextLinePart() to play the remainder of the line.
* @param InLine The input line
* @param Speed
*/
UFUNCTION(BlueprintCallable, Category = "Typewriter") UFUNCTION(BlueprintCallable, Category = "Typewriter")
void PlayLine(const FText& InLine, float Speed = 1.0f); void PlayLine(const FText& InLine, float Speed = 1.0f);
UFUNCTION(BlueprintCallable, Category = "Typewriter") UFUNCTION(BlueprintCallable, Category = "Typewriter")
void GetCurrentLine(FText& OutLine) const { OutLine = CurrentLine; } void GetCurrentLine(FText& OutLine) const { OutLine = CurrentLine; }
/// Return whether the entire line has finished playing
UFUNCTION(BlueprintCallable, Category = "Typewriter") UFUNCTION(BlueprintCallable, Category = "Typewriter")
bool HasFinishedPlayingLine() const { return bHasFinishedPlaying; } bool HasFinishedPlayingLine() const { return bHasFinishedPlaying; }
/// Returns whether the number of lines exceeded MaxNumberOfLines and there are still parts to play.
UFUNCTION(BlueprintCallable, Category = "Typewriter")
bool HasMoreLineParts() const { return bHasMoreLineParts; }
/// If HasMoreLineParts() is true, play the next part of the line originally requested by PlayLine
UFUNCTION(BlueprintCallable, Category = "Typewriter")
void PlayNextLinePart(float Speed = 1.0f);
UFUNCTION(BlueprintCallable, Category = "Typewriter") UFUNCTION(BlueprintCallable, Category = "Typewriter")
void SkipToLineEnd(); void SkipToLineEnd();
@ -127,15 +148,18 @@ protected:
private: private:
void PlayNextLetter(); void PlayNextLetter();
static bool IsSentenceTerminator(TCHAR Letter); static bool IsSentenceTerminator(TCHAR Letter);
static bool IsClauseTerminator(TCHAR Letter);
static int FindLastTerminator(const FString& CurrentLineString, int Count);
void CalculateWrappedString(); int CalculateMaxLength();
void CalculateWrappedString(const FString& CurrentLineString);
FString CalculateSegments(FString* OutCurrentRunName); FString CalculateSegments(FString* OutCurrentRunName);
void StartPlayLine(); void StartPlayLine();
UPROPERTY() UPROPERTY()
FText CurrentLine; FText CurrentLine;
FString RemainingLinePart;
struct FTypewriterTextSegment struct FTypewriterTextSegment
{ {
@ -158,6 +182,7 @@ private:
float CombinedTextHeight = 0; float CombinedTextHeight = 0;
uint32 bHasFinishedPlaying : 1; uint32 bHasFinishedPlaying : 1;
uint32 bHasMoreLineParts : 1;
FTimerHandle LetterTimer; FTimerHandle LetterTimer;
float CurrentPlaySpeed = 1; float CurrentPlaySpeed = 1;