1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-16 18:38:32 +08:00
Files
osu-lazer/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs
T
Bartłomiej Dach 5895a8ac49 Fix daily challenge marker text spacing
Closes https://github.com/ppy/osu/issues/32908.

Have you ever been in a situation wherein you find out you fixed a bug
that you didn't know existed, but that makes *another* bug appear
because it was relying on the other bug? This is where I'm at right now.

But, to start from the top.

`TextFlowContainer.Text` (the setter) is a convenience property that you
use to set the text in one go. Internally it uses `AddText()`:

	https://github.com/ppy/osu-framework/blob/681900ffb70adfeede4e3fa32a69da66252691ee/osu.Framework/Graphics/Containers/TextFlowContainer.cs#L81-L94

`AddText()`'s xmldoc says:

	The \n character will create a new paragraph, not just a line break.
	If you need \n to be a line break, use <see cref="AddParagraph{TSpriteText}(LocalisableString, Action{TSpriteText})"/> instead.

	https://github.com/ppy/osu-framework/blob/681900ffb70adfeede4e3fa32a69da66252691ee/osu.Framework/Graphics/Containers/TextFlowContainer.cs#L226-L239

That's right. This portion of xmldoc was *straight up false* and
*silently broken* before https://github.com/ppy/osu-framework/pull/6556.
If you want to check that out yourself, apply the following patch to
framework:

diff --git a/osu.Framework.Tests/Visual/Containers/TestSceneTextFlowContainer.cs b/osu.Framework.Tests/Visual/Containers/TestSceneTextFlowContainer.cs
index 464f47c2c..e1ad521a7 100644
--- a/osu.Framework.Tests/Visual/Containers/TestSceneTextFlowContainer.cs
+++ b/osu.Framework.Tests/Visual/Containers/TestSceneTextFlowContainer.cs
@@ -180,6 +180,22 @@ public void TestAlignmentIsCorrectWhenLineBreaksAtLastWordOfParagraph(Anchor tex
             });
         }

+        [Test]
+        public void TestSetTextWithNewLine()
+        {
+            AddStep("set text", () => textContainer.Text = "this text\nhas a newline");
+            AddStep("clear and add text", () =>
+            {
+                textContainer.Clear();
+                textContainer.AddText("this text\nhas a newline");
+            });
+            AddStep("clear and add paragraph", () =>
+            {
+                textContainer.Clear();
+                textContainer.AddParagraph("this text\nhas a newline");
+            });
+        }
+
         private void assertSpriteTextCount(int count)
             => AddAssert($"text flow has {count} sprite texts", () => textContainer.ChildrenOfType<SpriteText>().Count() == count);

On `master`, there will be a difference between the first two steps, and
the third. On 2025.321.0, *there will be none*.

My working theory as to why this was always busted is that the
corresponding code that was there before in

	https://github.com/bdach/osu-framework/blob/c31a48178889ca2f9b4d257d2d64915eee90338a/osu.Framework/Graphics/Containers/TextFlowContainer.cs#L454-L458

just straight up ran too late. *The height of the container is being
changed after the flow has laid itself out, without adjusting subsequent
children in any way.*

There is potentially a discussion to be had as to whether the emergent
behaviour of `TextFlowContainer.Text` with respect to `\n` character is
correct, but I'm just going to start with this diff and see what the
reaction is.
2025-04-22 08:52:01 +02:00

193 lines
7.2 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomTooltip<DailyChallengeTooltipData>
{
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
public DailyChallengeTooltipData? TooltipContent { get; private set; }
private OsuSpriteText dailyPlayCount = null!;
private Container content = null!;
private CircularContainer completionMark = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
OsuTextFlowContainer label;
InternalChildren = new Drawable[]
{
content = new Container
{
AutoSizeAxes = Axes.Both,
CornerRadius = 6,
BorderThickness = 2,
BorderColour = colourProvider.Background4,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(3f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
label = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f },
},
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
CornerRadius = 3,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
dailyPlayCount = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
UseFullGlyphHeight = false,
Colour = colourProvider.Content2,
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
},
}
},
}
},
}
},
completionMark = new CircularContainer
{
Alpha = 0,
Size = new Vector2(16),
Anchor = Anchor.TopRight,
Origin = Anchor.Centre,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Lime1,
},
new SpriteIcon
{
Size = new Vector2(8),
Colour = colourProvider.Background6,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Check,
}
}
},
};
// can't use this because osu-web does weird stuff with \\n.
// Text = UsersStrings.ShowDailyChallengeTitle.,
label.AddParagraph("Daily\nChallenge");
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(_ => updateDisplay(), true);
}
private void updateDisplay()
{
if (User.Value == null)
{
Hide();
return;
}
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
if (stats.PlayCount == 0)
{
Hide();
return;
}
dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0"));
dailyPlayCount.Colour = colours.ForRankingTier(DailyChallengeStatsTooltip.TierForPlayCount(stats.PlayCount));
bool playedToday = stats.LastUpdate?.Date == DateTimeOffset.UtcNow.Date;
bool userIsOnOwnProfile = stats.UserID == api.LocalUser.Value.Id;
if (playedToday && userIsOnOwnProfile)
{
if (completionMark.Alpha > 0.8f)
{
completionMark.ScaleTo(1.2f).ScaleTo(1, 800, Easing.OutElastic);
}
else
{
completionMark.FadeIn(500, Easing.OutExpo);
completionMark.ScaleTo(1.6f).ScaleTo(1, 500, Easing.OutExpo);
}
content.BorderColour = colours.Lime1;
}
else
{
completionMark.FadeOut(50);
content.BorderColour = colourProvider.Background4;
}
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
Show();
}
public ITooltip<DailyChallengeTooltipData> GetCustomTooltip() => new DailyChallengeStatsTooltip();
}
}