Skip to content

Make TextFlowContainer wrap on individual characters as a last resort - #6775

Open
srb2thepast wants to merge 2 commits into
ppy:masterfrom
srb2thepast:text-flow-individual-character-wrap-v6
Open

Make TextFlowContainer wrap on individual characters as a last resort#6775
srb2thepast wants to merge 2 commits into
ppy:masterfrom
srb2thepast:text-flow-individual-character-wrap-v6

Conversation

@srb2thepast

Copy link
Copy Markdown

There are a few aspects of this implementation that are worth a second opinion, like how part recreation must be done in LoadComplete() rather than LoadCompleteAsync() due to the container's size being unknown at that point. Open to heavy feedback on where/how to improve this and any other aspect of the PR.

Note: the "ChangeLocalisationAfterAsyncLoad" test case in TestSceneTextFlowContainerLocalisation.cs fails due to attempting to acquire parts before the container has called LoadComplete().

Video preview:

2026-07-16.20-16-06.mp4
2026-07-16.20-24-12.mp4

No AI was used at any point for this contribution.

[Test]
public void TestOverflowCharacterSplitting()
{
string overflowText = "LoremipsumdolorsitametconsecteturadipiscingelitIntegermattiseuturpisvitaeposuereOrcivariusnatoquepenatibusetmagnisdisparturientmontesnasceturridiculusmusEtiammaurisnibhfaucibusmaximusornareeuultricesutipsumProinrhoncusnuncetfaucibuspretiumnislnuncdapibusmassaetscelerisquenibhligulaidodioPraesentdapibusexsednuncegestasinplaceratrisusmattisNullasedligulavelitVestibulumauctorportaerosetcondimentumEtiamlaoreetnuncneclaciniapulvinarMaurishendreritmiataliquetcondimentumexexcursusdolornonportaeraterosidjustoCrasmalesuadatinciduntnuncattinciduntrisuseleifendidMaecenashendreritvenenatismietlobortisEtiamsemtortorelementumegetlacusnonportatristiquequamMorbisedlaciniaodioPhasellusutpretiumnuncFuscevitaemollismagnavelscelerisquedui";
Comment thread osu.Framework.Tests/Visual/Containers/TestSceneTextFlowContainer.cs Fixed
first = false;
}

void addCharacters(string text)
{
if (AutoSizeAxes.HasFlagFast(Axes.X))
return true;
if (Parent == null)
/// <returns>Whether the text fits within the bounds of this TextFlowContainer.</returns>
/// <exception cref="InvalidOperationException">If this container's <see cref="RelativeSizeAxes"/> == <see cref="Axes.X"/> and/or it hasn't loaded (Parent == null)</exception>
/// <remarks>The <paramref name="spriteText"/> provided will be pre-loaded by being passed into <see cref="CompositeDrawable.LoadComponent{TLoadable}(TLoadable)"/> to get its width.</remarks>
public bool TextFitsInFlow(SpriteText spriteText)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should definitely not be public unless there's a use case for it.

if (AutoSizeAxes.HasFlagFast(Axes.X))
return true;
if (Parent == null)
throw new InvalidOperationException($"Cannot invoke {nameof(TextFitsInFlow)} before this {nameof(TextFlowContainer)} has a parent. Consider calling after this container has loaded.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should definitely not throw because there is no way a user can fix this.

if (Parent == null)
throw new InvalidOperationException($"Cannot invoke {nameof(TextFitsInFlow)} before this {nameof(TextFlowContainer)} has a parent. Consider calling after this container has loaded.");
if (Flow.LoadState < LoadState.Ready)
throw new InvalidOperationException($"Cannot invoke {nameof(TextFitsInFlow)} before this container's {nameof(InnerFlow)} is ready. Consider calling after this container has loaded.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

as above


protected partial class InnerFlow : FillFlowContainer
{
protected internal void LoadSpriteTextComponent(SpriteText item)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this just cannot exist. you haven't even explained what or why or a

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This was included so the TextFitsInFlow method could know what a SpriteText's size would be in relation to the InnerFlow if it were added, without needing to actually add it as a child to the InnerFlow. (If a large SpriteText were unknowingly added as a child, it would be loaded only to be removed from InnerFlow soon after and have each of its characters re-created & added, leading to unwanted results like flashing.)

The tradeoffs of this may not be reasonably worth it though. I agree that its best for this not to be included.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TextFlowContainer should wrap on individual characters as a last resort

3 participants