diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs new file mode 100644 index 0000000000..5523d02694 --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.DailyChallenge; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeScoreBreakdown : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [Test] + public void TestBasicAppearance() + { + DailyChallengeScoreBreakdown breakdown = null!; + + AddStep("create content", () => Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + breakdown = new DailyChallengeScoreBreakdown + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + AddSliderStep("adjust width", 0.1f, 1, 1, width => + { + if (breakdown.IsNotNull()) + breakdown.Width = width; + }); + AddSliderStep("adjust height", 0.1f, 1, 1, height => + { + if (breakdown.IsNotNull()) + breakdown.Height = height; + }); + + AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1])); + AddStep("add new score", () => + { + var testScore = TestResources.CreateTestScoreInfo(); + testScore.TotalScore = RNG.Next(1_000_000); + + breakdown.AddNewScore(testScore); + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs new file mode 100644 index 0000000000..3d4f27c44b --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -0,0 +1,193 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeScoreBreakdown : CompositeDrawable + { + private FillFlowContainer barsContainer = null!; + + private const int bin_count = 13; + private long[] bins = new long[bin_count]; + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new SectionHeader("Score breakdown"), + barsContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Both, + Height = 0.9f, + Padding = new MarginPadding { Top = 35 }, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + } + }; + + for (int i = 0; i < bin_count; ++i) + { + LocalisableString? label = null; + + switch (i) + { + case 2: + case 4: + case 6: + case 8: + label = @$"{100 * i}k"; + break; + + case 10: + label = @"1M"; + break; + } + + barsContainer.Add(new Bar(label) + { + Width = 1f / bin_count, + }); + } + } + + public void AddNewScore(IScoreInfo scoreInfo) + { + int targetBin = (int)Math.Clamp(Math.Floor((float)scoreInfo.TotalScore / 100000), 0, bin_count - 1); + bins[targetBin] += 1; + updateCounts(); + + var text = new OsuSpriteText + { + Text = scoreInfo.TotalScore.ToString(@"N0"), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Default.With(size: 30), + RelativePositionAxes = Axes.X, + X = (targetBin + 0.5f) / bin_count - 0.5f, + Alpha = 0, + }; + AddInternal(text); + + Scheduler.AddDelayed(() => + { + float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y; + text.FadeInFromZero() + .ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf) + .MoveToY(startY) + .MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint) + .FadeOut(2500, Easing.OutQuint) + .Expire(); + }, 150); + } + + public void SetInitialCounts(long[] counts) + { + if (counts.Length != bin_count) + throw new ArgumentException(@"Incorrect number of bins.", nameof(counts)); + + bins = counts; + updateCounts(); + } + + private void updateCounts() + { + long max = bins.Max(); + for (int i = 0; i < bin_count; ++i) + barsContainer[i].UpdateCounts(bins[i], max); + } + + private partial class Bar : CompositeDrawable + { + private readonly LocalisableString? label; + + private long count; + private long max; + + public Container CircularBar { get; private set; } = null!; + + public Bar(LocalisableString? label = null) + { + this.label = label; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.Both; + + AddInternal(new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Bottom = 20, + Horizontal = 3, + }, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Masking = true, + Child = CircularBar = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.01f, + Masking = true, + CornerRadius = 10, + Colour = colourProvider.Highlight1, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }); + + if (label != null) + { + AddInternal(new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + Text = label.Value, + Colour = colourProvider.Content2, + }); + } + } + + protected override void Update() + { + base.Update(); + + CircularBar.CornerRadius = Math.Min(CircularBar.DrawHeight / 2, CircularBar.DrawWidth / 4); + } + + public void UpdateCounts(long newCount, long newMax) + { + bool isIncrement = newCount > count; + + count = newCount; + max = newMax; + + CircularBar.ResizeHeightTo(0.01f + 0.99f * count / max, 300, Easing.OutQuint); + if (isIncrement) + CircularBar.FlashColour(Colour4.White, 600, Easing.OutQuint); + } + } + } +}