// Copyright Epic Games, Inc. All Rights Reserved. #include "Widgets/Text/STextBlock.h" #include "SlateGlobals.h" #include "Framework/Text/PlainTextLayoutMarshaller.h" #include "Widgets/Text/SlateTextBlockLayout.h" #include "Types/ReflectionMetadata.h" #include "Rendering/DrawElements.h" #include "Framework/Application/SlateApplication.h" #include "Fonts/FontMeasure.h" #if WITH_ACCESSIBILITY #include "Widgets/Accessibility/SlateAccessibleWidgets.h" #endif DECLARE_CYCLE_STAT(TEXT("STextBlock::OnPaint Time"), Stat_SlateTextBlockOnPaint, STATGROUP_SlateVerbose) DECLARE_CYCLE_STAT(TEXT("STextBlock::ComputeDesiredSize"), Stat_SlateTextBlockCDS, STATGROUP_SlateVerbose) SLATE_IMPLEMENT_WIDGET(STextBlock) void STextBlock::PrivateRegisterAttributes(FSlateAttributeInitializer& AttributeInitializer) { //~ If we are in SimpleTextMode the invalidation reason can be different. struct FInvalidation { static EInvalidateWidgetReason GetInvalidationNoneIfSimpleTextMode(const SWidget& OwningWidget) { return static_cast(OwningWidget).bSimpleTextMode ? EInvalidateWidgetReason::None : EInvalidateWidgetReason::Layout; } static EInvalidateWidgetReason GetInvalidationPaintIfSimpleTextMode(const SWidget& OwningWidget) { return static_cast(OwningWidget).bSimpleTextMode ? EInvalidateWidgetReason::Paint : EInvalidateWidgetReason::Layout; } static void UpdateTextStyle(SWidget& OwningWidget) { // see ComputeDesiredSize. We call UpdateTextStyle when the style changes. static_cast(OwningWidget).bTextLayoutUpdateTextStyle = true; } static void UpdateDesiredSize(SWidget& OwningWidget) { // We call ComputeDesiredSize when the text changes. static_cast(OwningWidget).bTextLayoutUpdateDesiredSize = true; } }; SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, BoundText, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, Font, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, StrikeBrush, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, ColorAndOpacity, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, ShadowOffset, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, ShadowColorAndOpacity, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, HighlightColor, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, HighlightShape, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateTextStyle)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, HighlightText, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, WrapTextAt, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, AutoWrapText, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, WrappingPolicy, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, TransformPolicy, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, Margin, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, Justification, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, LineHeightPercentage, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, ApplyLineHeightToBottomLine, EInvalidateWidgetReason::Layout) .OnValueChanged(FSlateAttributeDescriptor::FAttributeValueChangedDelegate::CreateStatic(FInvalidation::UpdateDesiredSize)); SLATE_ADD_MEMBER_ATTRIBUTE_DEFINITION(AttributeInitializer, MinDesiredWidth, EInvalidateWidgetReason::Layout); } STextBlock::STextBlock() : BoundText(*this) , Font(*this) , StrikeBrush(*this) , ColorAndOpacity(*this) , ShadowOffset(*this, FVector2D::ZeroVector) , ShadowColorAndOpacity(*this, FLinearColor(ForceInit)) , HighlightColor(*this, FLinearColor(ForceInit)) , HighlightShape(*this) , HighlightText(*this) , WrapTextAt(*this, 0.0f) , AutoWrapText(*this, false) , WrappingPolicy(*this, ETextWrappingPolicy::DefaultWrapping) , TransformPolicy(*this) , Margin(*this) , Justification(*this) , LineHeightPercentage(*this, 1.0f) , ApplyLineHeightToBottomLine(*this, true) , MinDesiredWidth(*this, 0.0f) , Union_Flags(0) , bSimpleTextMode(false) { SetCanTick(false); bCanSupportFocus = false; bTextLayoutUpdateTextStyle = true; bTextLayoutUpdateDesiredSize = true; #if WITH_ACCESSIBILITY AccessibleBehavior = EAccessibleBehavior::Auto; bCanChildrenBeAccessible = false; #endif } STextBlock::~STextBlock() { // Needed to avoid "deletion of pointer to incomplete type 'FSlateTextBlockLayout'; no destructor called" error when using TUniquePtr } void STextBlock::Construct( const FArguments& InArgs ) { TextStyle = *InArgs._TextStyle; SetHighlightText(InArgs._HighlightText); SetWrapTextAt(InArgs._WrapTextAt); SetAutoWrapText(InArgs._AutoWrapText); SetWrappingPolicy(InArgs._WrappingPolicy); SetTransformPolicy(InArgs._TransformPolicy); SetMargin(InArgs._Margin); SetLineHeightPercentage(InArgs._LineHeightPercentage); SetApplyLineHeightToBottomLine(InArgs._ApplyLineHeightToBottomLine); SetJustification(InArgs._Justification); SetMinDesiredWidth(InArgs._MinDesiredWidth); SetFont(InArgs._Font); SetStrikeBrush(InArgs._StrikeBrush); SetColorAndOpacity(InArgs._ColorAndOpacity); SetShadowOffset(InArgs._ShadowOffset); SetShadowColorAndOpacity(InArgs._ShadowColorAndOpacity); SetHighlightColor(InArgs._HighlightColor); SetHighlightShape(InArgs._HighlightShape); bSimpleTextMode = InArgs._SimpleTextMode; if (InArgs._OnDoubleClicked.IsBound()) { SetOnMouseDoubleClick(InArgs._OnDoubleClicked); } SetText(InArgs._Text); //if(!bSimpleTextMode) { // We use a dummy style here (as it may not be safe to call the delegates used to compute the style), but the correct style is set by ComputeDesiredSize TextLayoutCache = MakeUnique(this, FTextBlockStyle::GetDefault(), InArgs._TextShapingMethod, InArgs._TextFlowDirection, FCreateSlateTextLayout(), FPlainTextLayoutMarshaller::Create(), InArgs._LineBreakPolicy); TextLayoutCache->SetDebugSourceInfo(TAttribute::Create(TAttribute::FGetter::CreateLambda([this] { return FReflectionMetaData::GetWidgetDebugInfo(this); }))); TextLayoutCache->SetTextOverflowPolicy(InArgs._OverflowPolicy.IsSet() ? InArgs._OverflowPolicy : TextStyle.OverflowPolicy); } } FSlateFontInfo STextBlock::GetFont() const { return GetFontRef(); } const FSlateFontInfo& STextBlock::GetFontRef() const { return bIsAttributeFontSet ? Font.Get() : TextStyle.Font; } const FSlateBrush* STextBlock::GetStrikeBrush() const { return bIsAttributeStrikeBrushSet ? StrikeBrush.Get() : &TextStyle.StrikeBrush; } FSlateColor STextBlock::GetColorAndOpacity() const { return GetColorAndOpacityRef(); } const FSlateColor& STextBlock::GetColorAndOpacityRef() const { return bIsAttributeColorAndOpacitySet ? ColorAndOpacity.Get() : TextStyle.ColorAndOpacity; } FVector2f STextBlock::GetShadowOffset() const { return bIsAttributeShadowOffsetSet ? UE::Slate::CastToVector2f(ShadowOffset.Get()) : FVector2f(TextStyle.ShadowOffset); } FLinearColor STextBlock::GetShadowColorAndOpacity() const { return GetShadowColorAndOpacityRef(); } const FLinearColor& STextBlock::GetShadowColorAndOpacityRef() const { return bIsAttributeShadowColorAndOpacitySet ? ShadowColorAndOpacity.Get() : TextStyle.ShadowColorAndOpacity; } FSlateColor STextBlock::GetHighlightColor() const { return bIsAttributeHighlightColorSet ? HighlightColor.Get() : TextStyle.HighlightColor; } const FSlateBrush* STextBlock::GetHighlightShape() const { return bIsAttributeHighlightShapeSet ? HighlightShape.Get() : &TextStyle.HighlightShape; } ETextTransformPolicy STextBlock::GetTransformPolicyImpl() const { return bIsAttributeTransformPolicySet ? TransformPolicy.Get() : TextStyle.TransformPolicy; } FMargin STextBlock::GetMargin() const { return Margin.Get(); } float STextBlock::GetMinDesiredWidth() const { return MinDesiredWidth.Get(); } void STextBlock::InvalidateText(EInvalidateWidgetReason InvalidateReason) { if (bSimpleTextMode && EnumHasAnyFlags(InvalidateReason, EInvalidateWidgetReason::Layout)) { CachedSimpleDesiredSize.Reset(); } Invalidate(InvalidateReason); } void STextBlock::SetText(TAttribute InText) { // Cache the IsBound. //When the attribute is not bound, we need to go through all the other bound property to check if it is bound. bIsAttributeBoundTextBound = InText.IsBound(); BoundText.Assign(*this, MoveTemp(InText)); } void STextBlock::SetHighlightText(TAttribute InText) { HighlightText.Assign(*this, MoveTemp(InText)); } int32 STextBlock::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const { SCOPE_CYCLE_COUNTER(Stat_SlateTextBlockOnPaint); if (bSimpleTextMode) { // Draw the optional shadow const FLinearColor LocalShadowColorAndOpacity = GetShadowColorAndOpacity(); const FVector2f LocalShadowOffset = GetShadowOffset(); const bool ShouldDropShadow = LocalShadowColorAndOpacity.A > 0.f && LocalShadowOffset.SizeSquared() > 0.f; const bool bShouldBeEnabled = ShouldBeEnabled(bParentEnabled); const FText& LocalText = BoundText.Get(); FSlateFontInfo LocalFont = GetFont(); if (ShouldDropShadow) { const int32 OutlineSize = LocalFont.OutlineSettings.OutlineSize; if (!LocalFont.OutlineSettings.bApplyOutlineToDropShadows) { LocalFont.OutlineSettings.OutlineSize = 0; } FSlateDrawElement::MakeText( OutDrawElements, LayerId, AllottedGeometry.ToOffsetPaintGeometry(LocalShadowOffset), LocalText, LocalFont, bShouldBeEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect, InWidgetStyle.GetColorAndOpacityTint() * LocalShadowColorAndOpacity ); // Restore outline size for main text LocalFont.OutlineSettings.OutlineSize = OutlineSize; // actual text should appear above the shadow ++LayerId; } // Draw the text itself FSlateDrawElement::MakeText( OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), LocalText, LocalFont, bShouldBeEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect, InWidgetStyle.GetColorAndOpacityTint() * GetColorAndOpacity().GetColor(InWidgetStyle) ); } else { const FVector2D LastDesiredSize = TextLayoutCache->GetDesiredSize(); // OnPaint will also update the text layout cache if required UpdateTextBlockLayout(TextLayoutCache->GetLayoutScale()); LayerId = TextLayoutCache->OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, ShouldBeEnabled(bParentEnabled)); // HACK: Due to the nature of wrapping and layout, we may have been arranged in a different box than what we were cached with. Which // might update wrapping, so make sure we always set the desired size to the current size of the text layout, which may have changed // during paint. const bool bCanWrap = WrapTextAt.Get() > 0 || AutoWrapText.Get(); const FVector2D NewDesiredSize = TextLayoutCache->GetDesiredSize(); if (bCanWrap && !NewDesiredSize.Equals(LastDesiredSize)) { const_cast(this)->Invalidate(EInvalidateWidgetReason::Layout); } } return LayerId; } FVector2D STextBlock::ComputeDesiredSize(float LayoutScaleMultiplier) const { SCOPE_CYCLE_COUNTER(Stat_SlateTextBlockCDS); if (bSimpleTextMode) { const FVector2D LocalShadowOffset = FVector2D(GetShadowOffset()); const float LocalOutlineSize = (float)(GetFont().OutlineSettings.OutlineSize); // Account for the outline width impacting both size of the text by multiplying by 2 // Outline size in Y is accounted for in MaxHeight calculation in Measure() const FVector2D ComputedOutlineSize(LocalOutlineSize * 2.f, LocalOutlineSize); const FVector2D TextSize = FSlateApplication::Get().GetRenderer()->GetFontMeasureService()->Measure(BoundText.Get(), GetFont()) + ComputedOutlineSize + LocalShadowOffset; CachedSimpleDesiredSize = FVector2f(FMath::Max(MinDesiredWidth.Get(), TextSize.X), TextSize.Y); return FVector2D(CachedSimpleDesiredSize.GetValue()); } else { bTextLayoutUpdateDesiredSize = true; // force ComputeDesiredSize UpdateTextBlockLayout(LayoutScaleMultiplier); const FVector2D TextSize = TextLayoutCache->GetDesiredSize(); return FVector2D(FMath::Max(MinDesiredWidth.Get(), TextSize.X), TextSize.Y); } } void STextBlock::SetFont(TAttribute InFont) { bIsAttributeFontSet = InFont.IsSet(); Font.Assign(*this, MoveTemp(InFont)); } void STextBlock::SetStrikeBrush(TAttribute InStrikeBrush) { bIsAttributeStrikeBrushSet = InStrikeBrush.IsSet(); StrikeBrush.Assign(*this, MoveTemp(InStrikeBrush)); } void STextBlock::SetColorAndOpacity(TAttribute InColorAndOpacity) { bIsAttributeColorAndOpacitySet = InColorAndOpacity.IsSet(); ColorAndOpacity.Assign(*this, MoveTemp(InColorAndOpacity)); } void STextBlock::SetTextStyle(const FTextBlockStyle* InTextStyle) { if (InTextStyle) { TextStyle = *InTextStyle; } else { FArguments Defaults; TextStyle = *Defaults._TextStyle; } InvalidateText(EInvalidateWidgetReason::Layout); } void STextBlock::SetTextShapingMethod(const TOptional& InTextShapingMethod) { if (!bSimpleTextMode) { TextLayoutCache->SetTextShapingMethod(InTextShapingMethod); InvalidateText(EInvalidateWidgetReason::Layout); } } void STextBlock::SetTextFlowDirection(const TOptional& InTextFlowDirection) { if(!bSimpleTextMode) { TextLayoutCache->SetTextFlowDirection(InTextFlowDirection); InvalidateText(EInvalidateWidgetReason::Layout); } } void STextBlock::SetWrapTextAt(TAttribute InWrapTextAt) { WrapTextAt.Assign(*this, MoveTemp(InWrapTextAt), 0.f); } void STextBlock::SetAutoWrapText(TAttribute InAutoWrapText) { AutoWrapText.Assign(*this, MoveTemp(InAutoWrapText), 0.f); } void STextBlock::SetWrappingPolicy(TAttribute InWrappingPolicy) { WrappingPolicy.Assign(*this, MoveTemp(InWrappingPolicy)); } void STextBlock::SetTransformPolicy(TAttribute InTransformPolicy) { bIsAttributeTransformPolicySet = InTransformPolicy.IsSet(); TransformPolicy.Assign(*this, MoveTemp(InTransformPolicy)); } void STextBlock::SetOverflowPolicy(TOptional InOverflowPolicy) { TextLayoutCache->SetTextOverflowPolicy(InOverflowPolicy); InvalidateText(EInvalidateWidgetReason::Layout); } void STextBlock::SetShadowOffset(TAttribute InShadowOffset) { bIsAttributeShadowOffsetSet = InShadowOffset.IsSet(); ShadowOffset.Assign(*this, MoveTemp(InShadowOffset)); } void STextBlock::SetShadowColorAndOpacity(TAttribute InShadowColorAndOpacity) { bIsAttributeShadowColorAndOpacitySet = InShadowColorAndOpacity.IsSet(); ShadowColorAndOpacity.Assign(*this, MoveTemp(InShadowColorAndOpacity)); } void STextBlock::SetHighlightColor(TAttribute InHighlightColor) { bIsAttributeHighlightColorSet = InHighlightColor.IsSet(); HighlightColor.Assign(*this, MoveTemp(InHighlightColor)); } void STextBlock::SetHighlightShape(TAttribute InHighlightShape) { bIsAttributeHighlightShapeSet = InHighlightShape.IsSet(); HighlightShape.Assign(*this, MoveTemp(InHighlightShape)); } void STextBlock::SetMinDesiredWidth(TAttribute InMinDesiredWidth) { MinDesiredWidth.Assign(*this, MoveTemp(InMinDesiredWidth), 0.f); } void STextBlock::SetLineHeightPercentage(TAttribute InLineHeightPercentage) { LineHeightPercentage.Assign(*this, MoveTemp(InLineHeightPercentage)); } void STextBlock::SetApplyLineHeightToBottomLine(TAttribute InApplyLineHeightToBottomLine) { ApplyLineHeightToBottomLine.Assign(*this, MoveTemp(InApplyLineHeightToBottomLine)); } void STextBlock::SetMargin(TAttribute InMargin) { Margin.Assign(*this, MoveTemp(InMargin)); } void STextBlock::SetJustification(TAttribute InJustification) { Justification.Assign(*this, MoveTemp(InJustification)); } FTextBlockStyle STextBlock::GetComputedTextStyle() const { FTextBlockStyle ComputedStyle = TextStyle; ComputedStyle.SetFont( GetFontRef() ); if (const FSlateBrush* const ComputedStrikeBrush = GetStrikeBrush()) { ComputedStyle.SetStrikeBrush(*ComputedStrikeBrush); } ComputedStyle.SetColorAndOpacity( GetColorAndOpacity() ); ComputedStyle.SetShadowOffset( GetShadowOffset() ); ComputedStyle.SetShadowColorAndOpacity( GetShadowColorAndOpacity() ); ComputedStyle.SetHighlightColor( GetHighlightColor() ); if (const FSlateBrush* const ComputedHighlightShape = GetHighlightShape()) { ComputedStyle.SetHighlightShape(*ComputedHighlightShape); } return ComputedStyle; } void STextBlock::UpdateTextBlockLayout(float LayoutScaleMultiplier) const { if (bTextLayoutUpdateTextStyle) { TextLayoutCache->UpdateTextStyle(GetComputedTextStyle()); } if (bTextLayoutUpdateDesiredSize || bTextLayoutUpdateTextStyle) { // ComputeDesiredSize will also update the text layout cache if required FSlateTextBlockLayout::FWidgetDesiredSizeArgs DesiredSizeArgs( BoundText.Get(), HighlightText.Get(), WrapTextAt.Get(), AutoWrapText.Get(), WrappingPolicy.Get(), GetTransformPolicyImpl(), Margin.Get(), LineHeightPercentage.Get(), ApplyLineHeightToBottomLine.Get(), Justification.Get() ); TextLayoutCache->ComputeDesiredSize(DesiredSizeArgs, LayoutScaleMultiplier); bTextLayoutUpdateDesiredSize = false; bTextLayoutUpdateTextStyle = false; } } #if WITH_ACCESSIBILITY TSharedRef STextBlock::CreateAccessibleWidget() { return MakeShareable(new FSlateAccessibleTextBlock(SharedThis(this))); } TOptional STextBlock::GetDefaultAccessibleText(EAccessibleType AccessibleType) const { return BoundText.Get(); } #endif