diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScoreDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScoreDisplay.cs new file mode 100644 index 000000000000..cdea32e2aac4 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScoreDisplay.cs @@ -0,0 +1,133 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Online.Leaderboards; +using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneLeaderboardScoreDisplay : OsuTestScene + { + private Container content = null!; + protected override Container Content => content; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create components", () => base.Content.Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 200, + Height = 50, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray5, + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + } + } + }); + } + + [Test] + public void TestBasic() + { + AddStep("create content", () => Child = new LeaderboardScoreDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = { Value = "1,000,000" }, + }); + } + + [Test] + public void TestCustomContent() + { + AddStep("create content", () => Child = new LeaderboardScoreDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = { Value = "1,000,000" }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(-10, 0), + Padding = new MarginPadding { Top = 4 }, + ChildrenEnumerable = new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() }.Select(mod => new ModIcon(mod) + { + Scale = new Vector2(0.3f), + Height = ModIcon.MOD_ICON_SIZE.Y * 3 / 4f, + }), + }, + }); + } + + [Test] + public void TestCustomContent2() + { + AddStep("create content", () => Child = new LeaderboardScoreDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = { Value = "1,000,000" }, + Child = new LeaderboardStatistic("Completed Beatmaps".ToUpperInvariant(), "2", false, 0) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Top = 5, Right = 1.5f }, + }, + }); + } + + [Test] + public void TestUpdateScore() + { + LeaderboardScoreDisplay display = null!; + Bindable score = null!; + + AddStep("create content", () => + { + score = new Bindable("1,000,000"); + + Child = display = new LeaderboardScoreDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = { BindTarget = score }, + }; + }); + AddUntilStep("score text is correct", () => display.ChildrenOfType().Single().Text.ToString(), () => Is.EqualTo("1,000,000")); + AddStep("update score", () => score.Value = "1,234,567"); + AddUntilStep("score text is correct", () => display.ChildrenOfType().Single().Text.ToString(), () => Is.EqualTo("1,234,567")); + } + } +} diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreDisplay.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreDisplay.cs new file mode 100644 index 000000000000..b5227c89bd52 --- /dev/null +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreDisplay.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Online.Leaderboards +{ + public partial class LeaderboardScoreDisplay : Container, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly Container content; + protected override Container Content => content; + + private readonly OsuSpriteText scoreText; + + public LeaderboardScoreDisplay() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, -2f), + Children = new Drawable[] + { + scoreText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + UseFullGlyphHeight = false, + Spacing = new Vector2(-1.5f), + Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), + }, + content = new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(score => scoreText.Text = score.NewValue, true); + } + } +} diff --git a/osu.Game/Online/Leaderboards/LeaderboardStatistic.cs b/osu.Game/Online/Leaderboards/LeaderboardStatistic.cs new file mode 100644 index 000000000000..da4a04542fc5 --- /dev/null +++ b/osu.Game/Online/Leaderboards/LeaderboardStatistic.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Online.Leaderboards +{ + public partial class LeaderboardStatistic : Container + { + public FillDirection Direction + { + get => fillFlowContainer.Direction; + set + { + fillFlowContainer.Direction = value; + updateDirection(); + } + } + + private readonly bool perfect; + + private readonly Container content; + private readonly FillFlowContainer fillFlowContainer; + + private readonly OsuSpriteText nameText; + private readonly OsuSpriteText valueText; + + public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); + + public LeaderboardStatistic(LocalisableString name, LocalisableString value, bool perfect, float? minWidth = null) + { + this.perfect = perfect; + + AutoSizeAxes = Axes.Both; + Child = content = new Container + { + AutoSizeAxes = Axes.Both, + Children = new[] + { + fillFlowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new[] + { + nameText = new OsuSpriteText + { + Text = name, + Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold), + }, + valueText = new OsuSpriteText + { + BypassAutoSizeAxes = Axes.X, + Text = value, + Font = OsuFont.Style.Body, + }, + } + }, + }, + }; + + if (minWidth != null) + Add(Empty().With(d => d.Width = minWidth.Value)); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + nameText.Colour = colourProvider.Content2; + valueText.Colour = perfect ? colours.Lime1 : Color4.White; + } + + private void updateDirection() + { + if (Direction == FillDirection.Vertical) + { + // We don't want the value setting the horizontal size, since it leads to wonky accuracy container length, + // since the accuracy is sometimes longer than its name. + valueText.BypassAutoSizeAxes = Axes.X; + + nameText.Anchor = nameText.Origin = Anchor.TopLeft; + valueText.Anchor = valueText.Origin = Anchor.TopLeft; + + fillFlowContainer.Spacing = Vector2.Zero; + } + else + { + // When laid out horizontally, both name and value need to contribute to the horizontal size. + valueText.BypassAutoSizeAxes = Axes.None; + + nameText.Anchor = nameText.Origin = Anchor.BottomLeft; + valueText.Anchor = valueText.Origin = Anchor.BottomLeft; + + fillFlowContainer.Spacing = new Vector2(5); + } + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapLeaderboardScore.cs b/osu.Game/Screens/Select/BeatmapLeaderboardScore.cs index bb96c97d520a..d66bb753e880 100644 --- a/osu.Game/Screens/Select/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/Select/BeatmapLeaderboardScore.cs @@ -324,9 +324,9 @@ private void load() Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ScoreComponentLabel(BeatmapsetsStrings.ShowScoreboardHeadersCombo.ToUpper(), $"{Score.MaxCombo.ToString()}x", + new LeaderboardStatistic(BeatmapsetsStrings.ShowScoreboardHeadersCombo.ToUpper(), $"{Score.MaxCombo.ToString()}x", Score.MaxCombo == Score.GetMaximumAchievableCombo(), 60), - new ScoreComponentLabel(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper(), Score.DisplayAccuracy, Score.Accuracy == 1, + new LeaderboardStatistic(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper(), Score.DisplayAccuracy, Score.Accuracy == 1, 55), }, Alpha = 0, @@ -423,35 +423,21 @@ private void load() RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(Score.Rank).Opacity(0.5f)), }, - new FillFlowContainer + new LeaderboardScoreDisplay { AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Direction = FillDirection.Vertical, Padding = new MarginPadding { Horizontal = corner_radius }, - Spacing = new Vector2(0f, -2f), - Children = new Drawable[] + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Current = scoreManager.GetBindableTotalScoreString(Score), + Child = modsContainer = new FillFlowContainer { - new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - UseFullGlyphHeight = false, - Current = scoreManager.GetBindableTotalScoreString(Score), - Spacing = new Vector2(-1.5f), - Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - }, - modsContainer = new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(-10, 0), - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(-10, 0), } } } @@ -659,55 +645,6 @@ public DateLabel(DateTimeOffset date) protected override LocalisableString Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); } - private partial class ScoreComponentLabel : Container - { - private readonly LocalisableString name; - private readonly LocalisableString value; - private readonly bool perfect; - private readonly float minWidth; - - private FillFlowContainer content = null!; - public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); - - public ScoreComponentLabel(LocalisableString name, LocalisableString value, bool perfect, float minWidth) - { - this.name = name; - this.value = value; - this.perfect = perfect; - this.minWidth = minWidth; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider colourProvider) - { - AutoSizeAxes = Axes.Both; - Child = content = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Colour = colourProvider.Content2, - Text = name, - Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold), - }, - new OsuSpriteText - { - // We don't want the value setting the horizontal size, since it leads to wonky accuracy container length, - // since the accuracy is sometimes longer than its name. - BypassAutoSizeAxes = Axes.X, - Text = value, - Font = OsuFont.Style.Body, - Colour = perfect ? colours.Lime1 : Color4.White, - }, - Empty().With(d => d.Width = minWidth), - } - }; - } - } - private partial class RankLabel : Container, IHasTooltip { private readonly bool darkText;