UMG: Visual artifacts when using auto wrap text

Simple situation: I have a text that get’s wrapped. However when I open the widget, the wrapping only happens after the text has been displayed for some time. It happens quite fast, so it’s not a gameplay issue, but certainly visually annyoing.

Those images should illustrate the issue:

When opening the widget:

38009-before.png

After a short amount of time:

38010-after_2.png

The widget opens and closes constantly (depending what button the mouse is hovering over), so the issue is soon noticed by the player.

Thanks for the quick and detailed response!

I think I’ll be able to work with that.

This is an unfortunate, but known, limitation of the auto-wrapping in Slate.

We can’t auto-wrap the text until we know how much space we have available, and we don’t know that until we’ve done a layout pass. This effectively means that the auto-wrapped text lags behind the desired widget size and can appear to snap when first created.

Are you able to use a fixed wrapping width instead? That doesn’t suffer from this issue as we know what width to wrap at when performing the first layout pass.

One more question tough:

I noticed the same behavior with hidden widgets. I have a widget which contains text. I hide the widget and at some point later in the game I change the text within the widget and display it again. The old text is displayed for a short time and then it changes to the new text.

Do you have any ideas on how to avoid this behavior?

Hmm, that’s odd. Text should update at the start of the layout pass.

How are you changing the text?

Is that done in Tick, or in response to some kind of event?

First, I set the necessary text:

Then I set the visibility of the box:

There is a little unrelated code in between, but those are the relevant bits.

This is done in Tick. The situation is like this:

I have multiple buttons in one widget and each tick I check wether the mouse hovers over one of them. If it does, I update the text of the info box, depending on the button the mouse hovers over, and set the box to visible.

The issue occurs when the mouse hovers over button A, then hovers over nothing and after that hovers over button B. The text of A gets displayed and quickly but not instantly changes to the text of B.

Sorry for the delay in getting back to you on this.

The reason this happens is because Tick is called after Slate has already done its pre-pass step. It is this step that updates the desired size of a widget before performing the rest of the layout and paint passes.

Since you’ve set the text after the widget has had its size cached, and since STextBlock only updates its text in ComputeDesiredSize, the visible text cached in the widget won’t be updated until the next pre-pass step (and even if it were updated immediately, the size would be wrong and you’d potentially get clipped text).

If there’s no better place for this logic than Tick, I’d suggest updating the text and then setting the widget to Hidden (as an aside Hidden, unlike Collapsed, will still have a pre-pass done on it). You can then set the visibility to Visible on the next Tick as the text will be up-to-date, although this may introduce a perceived lag in your UI.

Alternatively you could experiment with using bound functions for your text and visibility (although I’d suspect these would have the same issues as they’re not cached so the text would still change between pre-pass and paint), or you could use the “Force Layout Prepass” node to force your text to update (and the widget to re-size) after setting the new text.

Thank you very much, this is a very detailed and absolutely helpful response!

If I’m not mistaken I tried to use bound functions and they didn’t fix it either.

But I’ll definitely try the “Hidden for one more tick”-trick and if that lags, I’ll use “Force Layout Prepass”.

Sometimes it is also permanently reporting the wrong size, not just delayed by one tick. Even with fixed size wrapping.

Is there a way to force the recalculating of the size? I already tried in the tick:

InvalidateLayoutAndVolatility - no effect
SynchronizeProperties - no effect
Setting text to empty, then the text again - no effect
ForceLayoutPrepass - makes it worse, it will not even correct itself after 1 tick with that
SetVerticalAlignment - no effect
RebuildWidget - no effect
ComputeDesiredSize - no effect

There must be some code that is called 1 tick later which fixes it, but sometimes that code is not called and I need to call it manually. But what code is it? Maybe TextLayoutCache is permanently wrong? But I don’t see where it is ever refreshed and it is also not accessible from outside.

@ellocator Try ForceLayoutPrepass.

I further tracked the issue into the depth of the engine and it turns out the text field actually can calculate its correct size with ComputeDesiredSize. It’s just never used. Instead the SScaleBox parent sends the wrong AllottedGeometry with TheChild.Geometry to the STextBlock. So I will now investigate how to fix the scalebox to calculate the correct geometry.

Which version are you on?

4.18.2

I also now found a fix. The issue was in SScaleBox::OnArrangeChildren

As already known, the first desired size is wrong, that’s why another one is calculated in if(bRequiresAnotherPrepass). The issue was that the LastFinalOffset is calculated before that in if ( CurrentStretch != EStretch::Fill ).

So what I did was, swap the two blocks. Which means the LastFinalOffset is now calculated with the corrected desired Size in mind.

(The scalebox we use is EStretch::ScaleToFit and has bSingleLayoutPass=false, so it is doing that double size calculation every frame, but it seems to be tolerable performance-wise.

Also LastSlotWidgetDesiredSize remains the wrong size at the end of the function, but that does not seem to cause any problems.)