Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix text flow container layout changing after one frame in some cases #6556

Merged
merged 5 commits into from
Mar 24, 2025

Conversation

bdach
Copy link
Collaborator

@bdach bdach commented Mar 20, 2025

RFC. This probably needs more testing but I want to see what the initial review reaction to this is going to be.

Fixes https://discord.com/channels/90072389919997952/1327149041511043134/1351809773510590494. Probably also the issue mentioned in ppy/osu#32475 (comment), but I haven't checked.

Essentially what's happening here is that new lines have weird sizing rules wherein the height of the new line is relative to the height of the line preceding it. #6545 broke this because prior to it, the lines responsible for this, namely

if (c is NewLineContainer nlc)
{
nlc.Height = nlc.IndicatesNewParagraph ? (currentLineHeight == 0 ? lastLineHeight : currentLineHeight) * ParagraphSpacing : 0;
continue;
}

ran in TextFlowContainer.UpdateAfterChildren() before the base call, and because TextFlowContainer inherited FillFlowContainer, changing the inheritance to composition essentially reordered the call so that the height set happened after FillFlowContainer's layout logic ran, which is why the layout could be essentially wrong for a frame (with the new lines having height of zero).

After several hours of trying several hacks to jam this behaviour back where it should be, I noticed that the entire flow was just broken even before I started touching it. There were two completely independent invalidation paths (one in fill flow, one in text flow), and even before any of my recent changes, when (ab)using some rarely-used features like first line indent you could provoke the text flow to start twitching.

So what this does is that it takes the entirety of ComputeLayoutPositions() from fill flow, and adapts it to actually properly handle all of the features that text flow needs without post-facto position or margin adjusting hacks.

As much as I hate to say it, for review, it may be actually best to open FillFlowContainer.ComputeLayoutPositions() next to InnerFlow.ComputeLayoutPositions() added here and go through them both side-by-side, line-by-line.

bdach added 2 commits March 19, 2025 14:04
Fixes
https://discord.com/channels/90072389919997952/1327149041511043134/1351809773510590494.

Essentially what's happening here is that new lines have weird sizing
rules wherein the height of the new line is relative to the height of
the line preceding it. ppy#6545
broke this because prior to it, the lines responsible for this, namely

	https://github.com/bdach/osu-framework/blob/769a5d5803709a99ef92503d08c192f13e7d913e/osu.Framework/Graphics/Containers/TextFlowContainer.cs#L448-L452

ran in `TextFlowContainer.UpdateAfterChildren()` before the `base` call,
and because `TextFlowContainer` inherited `FillFlowContainer`, changing
the inheritance to composition essentially reordered the call so that
the height set happened *after* `FillFlowContainer`'s layout logic ran,
which is why the layout could be essentially wrong for a frame (with the
new lines having height of zero).

After several hours of trying several hacks to jam this behaviour back
where it should be, I noticed that the entire flow was just broken even
before I started touching it. There were two completely independent
invalidation paths (one in fill flow, one in text flow), and even before
any of my recent  changes, when (ab)using some rarely-used features like
first line indent you could provoke the text flow to start twitching.

So what this does is that it takes the entirety of
`ComputeLayoutPositions()` from fill flow, and adapts it to actually
properly handle all of the features that text flow needs without
post-facto position or margin adjusting hacks.
@bdach bdach requested a review from a team March 20, 2025 13:19
@bdach bdach self-assigned this Mar 20, 2025
Copy link
Contributor

@smoogipoo smoogipoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty rough, but aside from the issue it looks reasonable...

bdach added 2 commits March 24, 2025 09:44
This was failing 10 steps away from the actual problem due to
propagation of weird float values (infinities, NaNs, etc.) The actual
problem was that the offset logic was doing questionable things when the
flow was doing initial autosizing in the X direction. In that case the
maximum width was initialised to `float.MaxValue`, which then in
subsequent calculations got reduced to a very-large-but-finite X
position of everything in the flow, and things just broke.

To circumnavigate that, initialise max size using saner
`float.PositiveInfinity` values (which can be used as markers later),
store the actual widths of the lines rather than the offsets to the
right, and then in the second pass, account for the infinite expansion
due to autosize case by overwriting the max width with the width of the
broadest row.
Copy link
Contributor

@smoogipoo smoogipoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see how this goes :)

@smoogipoo smoogipoo merged commit 7df03d4 into ppy:master Mar 24, 2025
14 checks passed
@bdach bdach deleted the we-back-in-the-text-flow-mine branch March 24, 2025 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants