https://dev.epicgames.com/documentation/ko-kr/unreal-engine/umg-rich-text-blocks-in-unreal-engine
RichText를 사용하기 위해선
Data Table을 RichTextStyleRow로 생성하고, 행 이름을 설정하고 원하는 스타일을 설정 후,
일단 WBP에서 RichTextBlock을 넣어줘야 한다.
그 후, TextStyleSet를 만든 DT로 설정하고,
<RowName>원하는 Text</> 포멧으로 써주면 설정한 스타일로 바뀌는 것을 볼 수 있다.
이미지의 경우엔 Data Table을 RichImageRow로 생성하고, 역시나 마찬가지로 행 이름을 설정하고 원하는 Image를 설정해주면 된다.
이미지의 경우엔 RichTextBlockImageDecorator 클래스 BP를 만들어서 만든 DT를 설정해줘야 한다.
RichText의 Decorator에 방금 만든 Decorator를 설정 해주면 된다.
그 후, 이미지의 경우엔 포멧이 <img id="Row Name"/> 로 정해져 있으므로, 잘 따라서 치면 되겠다.
그런데 만약 Text와 Image를 한번에 넣고싶다면 어떻게 할까?
URichTextBlockImageDecorator클래스를 상속받아서 Custom Decorator를 만들어주면 된다.
우선 build.cs에 "UMG", "Slate", "SlateCore"를 추가해줘야 한다.
예시와 같이 <img id=""/>가 아닌 <custom decorator=""/>형식으로 Custom Tag를 적용하고 싶다면 상속받아서 정의해줘야 함.
name - custom
id - decorator로 설정하였다.
SCompoundWidget 클래스는 Slate 위젯을 커스텀 클래스로 뽑아내어 구성할 때 흔히 사용하는 베이스 클래스이다.
텍스트 중간에 삽입할 위젯을 하나의 위젯으로 묶어두고, 그 내부에 이미지 + 텍스트 등을 배치하고 싶기 때문에 사용하였다.
ChildSlot에 UBox -> UHorizontalBox -> SImage -> STextBlock으로 Horizontal Box에 Image와 Text가 같이 나오도록 설정하였다. 자기가 원하는 구조로 설정해주면 되겠다.
이제 만든 Custom TableRow인 FRichSomeWidgetRow로 Data Table을 만들어서 이미지와 텍스트를 설정해주자.
Custom URichTextBlockImageDecorator클래스의 BP를 만들고, FRichSomeWidgetRow로 만든 DataTable만 설정할 수 있도록 설정하였기 때문에 방금 만든 Data Table을 설정해주면 된다.
밑에 있는 Image Set는 기본 클래스에 있는 멤버 변수이므로 신경쓰지 않아도 된다.
이후, 역시나 WBP에서 Decorator를 설정해주고 설정한 Tag로 포멧에 맞게 써주면 된다!
밑에 사진을 보면 설정한 이미지와 텍스트가 같이 나오는 것을 확인할 수 있다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/RichTextBlockImageDecorator.h"
#include "MyRichTextBlockImageDecorator.generated.h"
class ISlateStyle;
USTRUCT(Blueprintable, BlueprintType)
struct FRichSomeWidgetRow : public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = Appearance)
FSlateBrush Brush;
UPROPERTY(EditAnywhere, Category = Appearance)
FText Text;
};
/**
*
*/
UCLASS()
class NST_API UMyRichTextBlockImageDecorator : public URichTextBlockImageDecorator
{
GENERATED_BODY()
public:
UMyRichTextBlockImageDecorator(const FObjectInitializer& ObjectInitializer);
virtual TSharedPtr<ITextDecorator> CreateDecorator(URichTextBlock* InOwner) override;
virtual const FRichSomeWidgetRow* FindSomeWidgetRow(FName TagOrId, bool bWarnIfMissing);
protected:
FRichSomeWidgetRow* FindRow(FName TagOrId, bool bWarnIfMissing);
UPROPERTY(EditAnywhere, Category = Appearance, meta = (RequiredAssetDataTags = "RowStructure=RichSomeWidgetRow"))
TObjectPtr<class UDataTable> MyImageSet;
};
#include "MyRichTextBlockImageDecorator.h"
#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"
class SRichInlineSomeWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SRichInlineSomeWidget)
{
}
SLATE_END_ARGS()
public:
void Construct(const FArguments& InArgs, const FRichSomeWidgetRow* Row, const FTextBlockStyle& TextStyle, TOptional<int32> Width, TOptional<int32> Height, EStretch::Type Stretch);
};
void SRichInlineSomeWidget::Construct(const FArguments& InArgs, const FRichSomeWidgetRow* Row, const FTextBlockStyle& TextStyle, TOptional<int32> Width, TOptional<int32> Height, EStretch::Type Stretch)
{
const FSlateBrush* InBrush = &(Row->Brush);
check(InBrush)
const FText InText = Row->Text;
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const float MaxHeight = FontMeasure->GetMaxCharacterHeight(TextStyle.Font, 1.0f);
float IconHeight = FMath::Max(MaxHeight, InBrush->ImageSize.Y);
if (Height.IsSet())
{
IconHeight = Height.GetValue();
}
float IconWidth = IconHeight;
if (Width.IsSet())
{
IconWidth = Width.GetValue();
}
ChildSlot
[
SNew(SBox)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SImage)
.Image(InBrush)
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(InText)
]
]
];
}
class FRichInlineSomeWidget : public FRichTextDecorator
{
public:
FRichInlineSomeWidget(URichTextBlock* InOwner, UMyRichTextBlockImageDecorator* InDecorator)
: FRichTextDecorator(InOwner)
, Decorator(InDecorator)
{
}
virtual bool Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const override;
protected:
virtual TSharedPtr<SWidget> CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const override;
private:
UMyRichTextBlockImageDecorator* Decorator;
};
bool FRichInlineSomeWidget::Supports(const FTextRunParseResults& RunParseResult, const FString& Text) const
{
bool Result = false;
const bool IsContainId = RunParseResult.MetaData.Contains(TEXT("decorator"));
const bool IsNameSomeWidget = RunParseResult.Name == TEXT("custom");
if (IsContainId && IsNameSomeWidget)
{
const FTextRange& IdRange = RunParseResult.MetaData[TEXT("decorator")];
const FString TagId = Text.Mid(IdRange.BeginIndex, IdRange.EndIndex - IdRange.BeginIndex);
const bool bWarnIfMissing = false;
Result = Decorator->FindSomeWidgetRow(*TagId, bWarnIfMissing) != nullptr;
}
return Result;
}
TSharedPtr<SWidget> FRichInlineSomeWidget::CreateDecoratorWidget(const FTextRunInfo& RunInfo, const FTextBlockStyle& TextStyle) const
{
TSharedPtr<SWidget> Result;
const bool bWarnIfMissing = true;
const FString IDString = RunInfo.MetaData[TEXT("decorator")];
const FRichSomeWidgetRow* SomeWidgetRow = Decorator->FindSomeWidgetRow(*IDString, bWarnIfMissing);
const FSlateBrush* Brush = &(SomeWidgetRow->Brush);
const FText Text = SomeWidgetRow->Text;
if (ensure(Brush))
{
TOptional<int32> Width;
if (const FString* WidthString = RunInfo.MetaData.Find(TEXT("width")))
{
int32 WidthTemp;
if (FDefaultValueHelper::ParseInt(*WidthString, WidthTemp))
{
Width = WidthTemp;
}
else if (FCString::Stricmp(GetData(*WidthString), TEXT("desired")) == 0)
{
Width = Brush->ImageSize.X;
}
}
TOptional<int32> Height;
if (const FString* HeightString = RunInfo.MetaData.Find(TEXT("height")))
{
int32 HeightTemp;
if (FDefaultValueHelper::ParseInt(*HeightString, HeightTemp))
{
Height = HeightTemp;
}
else if (FCString::Stricmp(GetData(*HeightString), TEXT("desired")) == 0)
{
Height = Brush->ImageSize.Y;
}
}
EStretch::Type Stretch = EStretch::ScaleToFit;
if (const FString* StretchString = RunInfo.MetaData.Find(TEXT("stretch")))
{
const UEnum* StretchEnum = StaticEnum<EStretch::Type>();
const int64 StretchValue = StretchEnum->GetValueByNameString(*StretchString);
if (StretchValue != INDEX_NONE)
{
Stretch = static_cast<EStretch::Type>(StretchValue);
}
}
Result = SNew(SRichInlineSomeWidget, SomeWidgetRow, TextStyle, Width, Height, Stretch);
}
return Result;
}
UMyRichTextBlockImageDecorator::UMyRichTextBlockImageDecorator(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
TSharedPtr<ITextDecorator> UMyRichTextBlockImageDecorator::CreateDecorator(URichTextBlock* InOwner)
{
return MakeShareable(new FRichInlineSomeWidget(InOwner, this));
}
const FRichSomeWidgetRow* UMyRichTextBlockImageDecorator::FindSomeWidgetRow(FName TagOrId, bool bWarnIfMissing)
{
return FindRow(TagOrId, bWarnIfMissing);
}
FRichSomeWidgetRow* UMyRichTextBlockImageDecorator::FindRow(FName TagOrId, bool bWarnIfMissing)
{
FRichSomeWidgetRow* Result = nullptr;
if (MyImageSet)
{
FString ContextString;
Result = MyImageSet->FindRow<FRichSomeWidgetRow>(TagOrId, ContextString, bWarnIfMissing);
}
return Result;
}