参考文档:
- 官方:https://www.unrealengine.com/zh-CN/tech-blog/advanced-text-styling-with-rich-text-block
- https://answers.unrealengine.com/questions/920385/umg-richtextblock-hyperlink-href-markup.html
- https://blog.csdn.net/u013507300/article/details/105205110
Epic提供了一个ImageDecorator的实现,我们可以模仿它来实现自定义Decorator。
剖析RichTextBlockImageDecorator
RichTextBlockImageDecorator.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "Fonts/SlateFontInfo.h"
#include "Styling/SlateTypes.h"
#include "Framework/Text/TextLayout.h"
#include "Framework/Text/ISlateRun.h"
#include "Framework/Text/ITextDecorator.h"
#include "Components/RichTextBlockDecorator.h"
#include "Engine/DataTable.h"
#include "RichTextBlockImageDecorator.generated.h"
class ISlateStyle;
/** Simple struct for rich text styles */
/** Data的数据行定义,这里只含有一个FSlateBrush*/
USTRUCT(Blueprintable, BlueprintType)
struct UMG_API FRichImageRow : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(EditAnywhere, Category = Appearance)
FSlateBrush Brush;
};
/**
* Allows you to setup an image decorator that can be configured
* to map certain keys to certain images. We recommend you subclass this
* as a blueprint to configure the instance.
*
* Understands the format <img id="NameOfBrushInTable"></>
*/
/** 图片装饰器类,可以在RichTextBlock的Decorator详情里看到UPROPERTY */
UCLASS(Abstract, Blueprintable)
class UMG_API URichTextBlockImageDecorator : public URichTextBlockDecorator
{
GENERATED_BODY()
public:
URichTextBlockImageDecorator(const FObjectInitializer& ObjectInitializer);
// 必须实现这个重写
virtual TSharedPtr<ITextDecorator> CreateDecorator(URichTextBlock* InOwner) override;
//找到数据表中相应的数据,即我们设置的FSlateBrush
virtual const FSlateBrush* FindImageBrush(FName TagOrId, bool bWarnIfMissing);
protected:
// 找到数据表中对应的行
FRichImageRow* FindImageRow(FName TagOrId, bool bWarnIfMissing);
// 数据表引用
UPROPERTY(EditAnywhere, Category=Appearance, meta=(RowType="RichImageRow"))
class UDataTable* ImageSet;
};
RichTextBlockImageDecorator.cpp
除了实现RichTextBlockImageDecorator之外,还定义并实现了两个类,分别是SRichInlineImage和FRichInlineImage。其中,SRichInlineImage为Slate,负责标记文本解析后的UI渲染;FRichInlineImage负责对标记进行实际的解析/替换。通过阅读后面的代码我们可以知道ImageDecorator支持的标签为:<img id="",width= ,height= ,strecth= />
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Components/RichTextBlockImageDecorator.h"
#include "UObject/SoftObjectPtr.h"
#include "Rendering/DrawElements.h"
#include "Framework/Text/SlateTextRun.h"
#include "Framework/Text/SlateTextLayout.h"
#include "Slate/SlateGameResources.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Framework/Application/SlateApplication.h"
#include "Fonts/FontMeasure.h"
#include "Math/UnrealMathUtility.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SScaleBox.h"
#include "Widgets/Layout/SBox.h"
#include "Misc/DefaultValueHelper.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/Package.h"
#define LOCTEXT_NAMESPACE "UMG"
/** Slate !!不熟啊 ~~~~*/
class SRichInlineImage : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SRichInlineImage)
{}
SLATE_END_ARGS()
public:
void Construct(const FArguments& InArgs, const FSlateBrush* Brush, const FTextBlockStyle& TextStyle, TOptional<int32> Width, TOptional<int32> Height, EStretch::Type Stretch)
{
if (ensure(Brush))
{
//获取FontMeasureService实例
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
// 富文本图片高度:取字体高度、Brush中ImageSize的Y值二者中更小的值,即图片大小受两个参数影响
float IconHeight = FMath::Min((float)FontMeasure->GetMaxCharacterHeight(TextStyle.Font, 1.0f), Brush->ImageSize.Y);
float IconWidth = IconHeight;
// 还可以直接传入宽高值
if (Width.IsSet())
{
IconWidth = Width.GetValue();
}
if (Height.IsSet())
{
IconHeight = Height.GetValue();
}
// 开始绘制
ChildSlot
[
SNew(SBox)
.HeightOverride(IconHeight)
.WidthOverride(IconWidth)
[
SNew(SScaleBox)
.Stretch(Stretch)
.StretchDirection(EStretchDirection::DownOnly)
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(Brush)
]
]
];
}
}
};
class FRichInlineImage : public FRichTextDecorator
{
public:
//构造函数,先调用父类构造函数,然后传入Decorator指针
FRichInlineImage(URichTextBlock* InOwner, URichTextBlockImageDecorator* InDecorator)
: FRichTextDecorator(InOwner)
, Decorator(InDecorator)
{
}
/**解析文本,如果返回true则表明支持这个标签文本,false则当作普通文字
我们使用ImageDecorator的标签文本为: <img id="RowName"/> ,所以我们可以大致了解Parser是如何工作的,当然如果你想的话,你还可以自定义Parser (⊙﹏⊙)
*/
virtual bool Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const override
{
if (RunParseResult.Name == TEXT("img") && RunParseResult.MetaData.Contains(TEXT("id")))
{
const FTextRange& IdRange = RunParseResult.MetaData[TEXT("id")];
//Text.Mid:取子串。这里取出Id值
const FString TagId = Text.Mid(IdRange.BeginIndex, IdRange.EndIndex - IdRange.BeginIndex);
const bool bWarnIfMissing = false;
return Decorator->FindImageBrush(*TagId, bWarnIfMissing) != nullptr;
}
return false;
}
protected:
/** 显然,只有当Support方法返回true之后,才会执行。找到我们在DataTable中配置的数据,用以生成Slate
*/
virtual TSharedPtr<SWidget> CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const override
{
const bool bWarnIfMissing = true;
const FSlateBrush* Brush = Decorator->FindImageBrush(*RunInfo.MetaData[TEXT("id")], bWarnIfMissing);
// 除了id之外,我们还可以在标签中写 width= ,height=,strech=
TOptional<int32> Width;
if (const FString* WidthString = RunInfo.MetaData.Find(TEXT("width")))
{
int32 WidthTemp;
Width = FDefaultValueHelper::ParseInt(*WidthString, WidthTemp) ? WidthTemp : TOptional<int32>();
}
TOptional<int32> Height;
if (const FString* HeightString = RunInfo.MetaData.Find(TEXT("height")))
{
int32 HeightTemp;
Height = FDefaultValueHelper::ParseInt(*HeightString, HeightTemp) ? HeightTemp : TOptional<int32>();
}
EStretch::Type Stretch = EStretch::ScaleToFit;
if (const FString* SstretchString = RunInfo.MetaData.Find(TEXT("stretch")))
{
static const UEnum* StretchEnum = StaticEnum<EStretch::Type>();
int64 StretchValue = StretchEnum->GetValueByNameString(*SstretchString);
if (StretchValue != INDEX_NONE)
{
Stretch = static_cast<EStretch::Type>(StretchValue);
}
}
// 使用解析的数据构造Slate
return SNew(SRichInlineImage, Brush, TextStyle, Width, Height, Stretch);
}
private:
URichTextBlockImageDecorator* Decorator;
};
/////////////////////////////////////////////////////
// URichTextBlockImageDecorator
URichTextBlockImageDecorator::URichTextBlockImageDecorator(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
const FSlateBrush* URichTextBlockImageDecorator::FindImageBrush(FName TagOrId, bool bWarnIfMissing)
{
const FRichImageRow* ImageRow = FindImageRow(TagOrId, bWarnIfMissing);
if (ImageRow)
{
return &ImageRow->Brush;
}
return nullptr;
}
FRichImageRow* URichTextBlockImageDecorator::FindImageRow(FName TagOrId, bool bWarnIfMissing)
{
if (ImageSet)
{
FString ContextString;
return ImageSet->FindRow<FRichImageRow>(TagOrId, ContextString, bWarnIfMissing);
}
return nullptr;
}
// 必须实现,创建FRichInlineImage用来解析文本
TSharedPtr<ITextDecorator> URichTextBlockImageDecorator::CreateDecorator(URichTextBlock* InOwner)
{
return MakeShareable(new FRichInlineImage(InOwner, this));
}
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE
超链接Decorator:
上面的参考文章中有详细的代码可供查阅。引擎提供了SRichTextHyperlink
和FHyperlinkStyle
供使用,如果不能满足你的需求,那么你必须先好好学习Slate。