I’ve been working on a custom rich text decorator that embeds more than just lightly styled text or an image and have run into a slate chicken and egg impasse that I don’t know how to resolve.
When I ‘inject’ a compound widget into my rich text block, the compound widget doesn’t know it’s own size as it’s textual content is supposed to wrap and it’s and decorative content is supposed to horizontally fill the parent rich text block.
I figured I’d need to explicitly pass-in or set the child’s size when calling SAssignNew
or defining the FWidgetRunInfo
object but both the parent and child don’t have valid sizes calculated yet.
I’ve tried calling SWidget->ArrangeChildren()
and/or SWidget->SlatePrepass()
to force the matter without success as afaik the child<->parent hierarchy doesn’t exist by the time ITextDecorator::Create
returns.
The stripped bare test article below is intended to replace the wrapped text with a simple overlay slot that contains both a tiling background image as well as the original text.
The compound widget
class SInjectedElement : public SCompoundWidget {
public:
SLATE_BEGIN_ARGS(SInjectedElement) {}
SLATE_END_ARGS()
void Construct(
const FArguments& InArgs,
const FText& Text,
const FString& TagName,
const FTextBlockStyle& TextStyle) {
if (!Text.IsEmptyOrWhitespace()) {
const float lineHeight = FSlateApplication::Get().GetRenderer()->GetFontMeasureService()->GetMaxCharacterHeight(TextStyle.Font, 1.0f);
FSlateBrush* img = FCoreStyle::Get().GetDefaultBrush();
//Size is technically irrelevant as it should tile to fill.
img->SetImageSize(FVector2D(2.0f, 2.0f));
img->Tiling = ESlateBrushTileType::Both;
img->DrawAs = ESlateBrushDrawType::Image;
//Inadvertently created text 'highlighting' while making the minimal test article
ChildSlot
.VAlign(VAlign_Top)
.HAlign(HAlign_Fill)
[
SNew(SOverlay)
+ SOverlay::Slot()
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
[
SNew(SImage)
.Image(img)
.ColorAndOpacity(FPaletteColors::WHITE)
]
+ SOverlay::Slot()
.VAlign(VAlign_Top)
.HAlign(HAlign_Center)
[
SNew(STextBlock)
.Justification(ETextJustify::Center)
.Text(Text)
.TextStyle(&TextStyle)
.AutoWrapText(true)
.WrappingPolicy(ETextWrappingPolicy::DefaultWrapping)
]
];
}
}
};
RichTextDecorator’s Create Function
TSharedRef<ISlateRun> FVTSRichTextDecorator::Create(
const TSharedRef<class FTextLayout>& TextLayout,
const FTextRunParseResults& RunParseResult,
const FString& OriginalText,
const TSharedRef<FString>& InOutModelText,
const ISlateStyle* Style) {
FTextRange range;
//Populate start of the range
range.BeginIndex = InOutModelText->Len();
//Store the name and content of the run
FTextRunInfo runInfo(RunParseResult.Name, FText::FromString(
OriginalText.Mid(RunParseResult.ContentRange.BeginIndex, RunParseResult.ContentRange.EndIndex - RunParseResult.ContentRange.BeginIndex)
));
//store a reference to the current default style.
const FTextBlockStyle& textStyle = owner->GetCurrentDefaultTextStyle();
TSharedPtr<ISlateRun> slateRun;
if (runInfo.Name == TEXT("Inject")) {
TSharedPtr<SWidget> decoWidget;
//Create the slate widget structure to inject
SAssignNew(decoWidget, SInjectedElement, runInfo.Content, runInfo.Name, textStyle);
//Without this nothing renders...
*InOutModelText += TEXT('\u200B');
//Set the end of the range
range.EndIndex = InOutModelText->Len();
//Calculate the baseline of the owning rich text block so that the injected widget has the same baseline.
const FSlateFontInfo font = textStyle.Font;
const float ShadowOffsetY = FMath::Min(0.0f, textStyle.ShadowOffset.Y);
TAttribute<int16> GetBaseline = TAttribute<int16>::CreateLambda([font, ShadowOffsetY]() {
const TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
return FontMeasure->GetBaseline(font) - ShadowOffsetY;
}
);
//FIXME: Always zero desire.
// Width from owners width.
FVector2D ownerSize = owner->GetDesiredSize();
// Height from contents height
FVector2D childSize = decoWidget->GetDesiredSize();
//FVector2D runSize = FVector2D(ownerSize.X, childSize.Y);
//Use a widget run to draw the created decorator widget.
FSlateWidgetRun::FWidgetRunInfo widgetRunInfo(decoWidget.ToSharedRef(), GetBaseline);// , GetSize);
slateRun = FSlateWidgetRun::Create(TextLayout, runInfo, InOutModelText, widgetRunInfo, range);
} else {
//Copies the run's content to the output as straight text
*InOutModelText += runInfo.Content.ToString();
range.EndIndex = InOutModelText->Len();
//Use a text run to draw the styled text.
slateRun = FSlateTextRun::Create(runInfo, InOutModelText, textStyle, range);
}
return slateRun.ToSharedRef();
}