void UTextRenderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { if (!TextLastUpdate.IdenticalTo(Text)) { // The pointer used by the bound text has changed, however the text may still be the same - check that now if (!TextLastUpdate.IsDisplayStringEqualTo(Text)) { // The source text has changed, so we need to update our render data MarkRenderStateDirty(); } // Update this even if the text is lexically identical, as it will update the pointer compared by IdenticalTo for the next Tick TextLastUpdate = FTextSnapshot(Text); } }
UTextRenderComponent::UTextRenderComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // Structure to hold one-time initialization struct FConstructorStatics { ConstructorHelpers::FObjectFinderOptional<UFont> Font; ConstructorHelpers::FObjectFinderOptional<UMaterial> TextMaterial; FConstructorStatics() : Font(TEXT("/Engine/EngineFonts/RobotoDistanceField")) , TextMaterial(TEXT("/Engine/EngineMaterials/DefaultTextMaterialOpaque")) { } }; static FConstructorStatics ConstructorStatics; PrimaryComponentTick.bCanEverTick = true; bTickInEditor = true; Text = LOCTEXT("DefaultText", "Text"); TextLastUpdate = FTextSnapshot(Text); Font = ConstructorStatics.Font.Get(); TextMaterial = ConstructorStatics.TextMaterial.Get(); SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); TextRenderColor = FColor::White; XScale = 1; YScale = 1; HorizSpacingAdjust = 0; HorizontalAlignment = EHTA_Left; VerticalAlignment = EVRTA_TextBottom; bGenerateOverlapEvents = false; if( Font ) { Font->ConditionalPostLoad(); WorldSize = Font->GetMaxCharHeight(); InvDefaultSize = 1.0f / WorldSize; } else { WorldSize = 30.0f; InvDefaultSize = 1.0f / 30.0f; } }
void FTextFormatData::ConditionalCompile_NoLock() { // IdenticalTo compares our pointer against the static empty instance, rather than checking if our text is actually empty // This is what we want to happen since a text using the static empty instance will never become non-empty, but an empty string might (due to a culture change, or in-editor change) bool bRequiresCompile = SourceType == ESourceType::Text && !SourceText.IdenticalTo(FText::GetEmpty()); if (bRequiresCompile) { if (!CompiledTextSnapshot.IdenticalTo(SourceText)) { if (!CompiledTextSnapshot.IsDisplayStringEqualTo(SourceText)) { bRequiresCompile = true; } CompiledTextSnapshot = FTextSnapshot(SourceText); // Update this even if the text is lexically identical, as it will update the pointer compared by IdenticalTo for the next ConditionalCompile } } if (bRequiresCompile) { Compile_NoLock(); } }
void FTextFormatData::Compile_NoLock() { LexedExpression.Reset(); if (SourceType == ESourceType::Text) { SourceExpression = SourceText.ToString(); CompiledTextSnapshot = FTextSnapshot(SourceText); } CompiledExpressionType = FTextFormat::EExpressionType::Simple; BaseFormatStringLength = 0; FormatArgumentEstimateMultiplier = 1; TValueOrError<TArray<FExpressionToken>, FExpressionError> Result = ExpressionParser::Lex(*SourceExpression, FTextFormatter::Get().GetTextFormatDefinitions()); bool bValidExpression = Result.IsValid(); if (bValidExpression) { LexedExpression = Result.StealValue(); // Quickly make sure the tokens are valid (argument modifiers may only follow an argument token) for (int32 TokenIndex = 0; TokenIndex < LexedExpression.Num(); ++TokenIndex) { const FExpressionToken& Token = LexedExpression[TokenIndex]; if (const auto* Literal = Token.Node.Cast<TextFormatTokens::FStringLiteral>()) { BaseFormatStringLength += Literal->StringLen; } else if (auto* Escaped = Token.Node.Cast<TextFormatTokens::FEscapedCharacter>()) { BaseFormatStringLength += 1; } else if (const auto* ArgumentToken = Token.Node.Cast<TextFormatTokens::FArgumentTokenSpecifier>()) { CompiledExpressionType = FTextFormat::EExpressionType::Complex; if (LexedExpression.IsValidIndex(TokenIndex + 1)) { const FExpressionToken& NextToken = LexedExpression[TokenIndex + 1]; // Peek to see if the next token is an argument modifier if (const auto* ArgumentModifierToken = NextToken.Node.Cast<TextFormatTokens::FArgumentModifierTokenSpecifier>()) { int32 ArgModLength = 0; bool ArgModUsesFormatArgs = false; ArgumentModifierToken->TextFormatArgumentModifier->EstimateLength(ArgModLength, ArgModUsesFormatArgs); BaseFormatStringLength += ArgModLength; FormatArgumentEstimateMultiplier += (ArgModUsesFormatArgs) ? 1 : 0; ++TokenIndex; // walk over the argument token so that the next iteration will skip over the argument modifier continue; } } } else if (Token.Node.Cast<TextFormatTokens::FArgumentModifierTokenSpecifier>()) { // Unexpected argument modifier token! const FText ErrorSourceText = FText::FromString(Token.Context.GetString()); Result = MakeError(FExpressionError(FText::Format(LOCTEXT("UnexpectedArgumentModifierToken", "Unexpected 'argument modifier' token: {0} (token started at index {1})"), ErrorSourceText, Token.Context.GetCharacterIndex()))); bValidExpression = false; break; } } } if (!bValidExpression) { LexedExpression.Reset(); CompiledExpressionType = FTextFormat::EExpressionType::Invalid; UE_LOG(LogTextFormatter, Warning, TEXT("Failed to compile text format string '%s': %s"), *SourceExpression, *Result.GetError().Text.ToString()); } }
void UTextRenderComponent::K2_SetText(const FText& Value) { Text = Value; TextLastUpdate = FTextSnapshot(Text); // update this immediately as we're calling MarkRenderStateDirty ourselves MarkRenderStateDirty(); }