diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs index df970c1c46..d596def98a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; @@ -24,9 +25,8 @@ namespace osu.Game.Tests.Visual.Gameplay Add(leaderboard = new TestGameplayLeaderboard { Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.TopCentre, Scale = new Vector2(2), - RelativeSizeAxes = Axes.X, }); } @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay playerScore.Value = 1222333; }); - AddStep("add player user", () => leaderboard.AddPlayer(playerScore, new User { Username = "You" })); + AddStep("add local player", () => leaderboard.Add(createLeaderboardScore(playerScore, "You", true))); AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v); } @@ -49,8 +49,8 @@ namespace osu.Game.Tests.Visual.Gameplay var player2Score = new BindableDouble(1234567); var player3Score = new BindableDouble(1111111); - AddStep("add player 2", () => leaderboard.AddPlayer(player2Score, new User { Username = "Player 2" })); - AddStep("add player 3", () => leaderboard.AddPlayer(player3Score, new User { Username = "Player 3" })); + AddStep("add player 2", () => leaderboard.Add(createLeaderboardScore(player2Score, "Player 2"))); + AddStep("add player 3", () => leaderboard.Add(createLeaderboardScore(player3Score, "Player 3"))); AddAssert("is player 2 position #1", () => leaderboard.CheckPositionByUsername("Player 2", 1)); AddAssert("is player position #2", () => leaderboard.CheckPositionByUsername("You", 2)); @@ -67,6 +67,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("is player 2 position #3", () => leaderboard.CheckPositionByUsername("Player 2", 3)); } + [Test] + public void TestRandomScores() + { + int playerNumber = 1; + AddRepeatStep("add player with random score", () => leaderboard.Add(createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), $"Player {playerNumber++}")), 10); + } + + private static GameplayLeaderboardScore createLeaderboardScore(BindableDouble score, string username, bool localOrReplayPlayer = false) + { + return new GameplayLeaderboardScore(new User { Username = username }, localOrReplayPlayer) + { + TotalScore = { BindTarget = score }, + }; + } + private class TestGameplayLeaderboard : GameplayLeaderboard { public bool CheckPositionByUsername(string username, int? expectedPosition) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 3934a99221..573bf54b14 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -2,11 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using JetBrains.Annotations; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Users; using osuTK; namespace osu.Game.Screens.Play.HUD @@ -15,8 +12,7 @@ namespace osu.Game.Screens.Play.HUD { public GameplayLeaderboard() { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; + AutoSizeAxes = Axes.Both; Direction = FillDirection.Vertical; @@ -26,35 +22,15 @@ namespace osu.Game.Screens.Play.HUD LayoutEasing = Easing.OutQuint; } - /// - /// Adds a player to the leaderboard. - /// - /// The bindable current score of the player. - /// The player. - public void AddPlayer([NotNull] IBindableNumber currentScore, [NotNull] User user) + public override void Add(GameplayLeaderboardScore drawable) { - var scoreItem = addScore(currentScore.Value, user); - currentScore.ValueChanged += s => scoreItem.TotalScore = s.NewValue; - } - - private GameplayLeaderboardScore addScore(double totalScore, User user) - { - var scoreItem = new GameplayLeaderboardScore - { - User = user, - TotalScore = totalScore, - OnScoreChange = updateScores, - }; - - Add(scoreItem); - updateScores(); - - return scoreItem; + base.Add(drawable); + drawable?.TotalScore.BindValueChanged(_ => updateScores(), true); } private void updateScores() { - var orderedByScore = this.OrderByDescending(i => i.TotalScore).ToList(); + var orderedByScore = this.OrderByDescending(i => i.TotalScore.Value).ToList(); for (int i = 0; i < Count; i++) { diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 4c75f422c9..439210c944 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -1,25 +1,34 @@ // 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 Humanizer; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using osu.Game.Utils; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { public class GameplayLeaderboardScore : CompositeDrawable { - private readonly OsuSpriteText positionText, positionSymbol, userString; - private readonly GlowingSpriteText scoreText; + private const float regular_width = 215f; + private const float extended_width = 235f; - public Action OnScoreChange; + private const float panel_height = 35f; + + private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText; + + public readonly BindableDouble TotalScore = new BindableDouble(1000000); + public readonly BindableDouble Accuracy = new BindableDouble(1); + public readonly BindableInt Combo = new BindableInt(); private int? scorePosition; @@ -34,103 +43,211 @@ namespace osu.Game.Screens.Play.HUD positionText.Text = $"#{scorePosition.Value.ToMetric(decimals: scorePosition < 100000 ? 1 : 0)}"; positionText.FadeTo(scorePosition.HasValue ? 1 : 0); - positionSymbol.FadeTo(scorePosition.HasValue ? 1 : 0); + updateColour(); } } - private double totalScore; + public User User { get; } - public double TotalScore + private readonly bool trackedPlayer; + + private Container mainFillContainer; + private Box centralFill; + + /// + /// Creates a new . + /// + /// The score's player. + /// Whether the player is the local user or a replay player. + public GameplayLeaderboardScore(User user, bool trackedPlayer) + { + User = user; + this.trackedPlayer = trackedPlayer; + + AutoSizeAxes = Axes.X; + Height = panel_height; + + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateColour(); + FinishTransforms(true); + } + + private void updateColour() + { + if (scorePosition == 1) + { + mainFillContainer.ResizeWidthTo(extended_width, 200, Easing.OutQuint); + panelColour = Color4Extensions.FromHex("7fcc33"); + textColour = Color4.White; + } + else if (trackedPlayer) + { + mainFillContainer.ResizeWidthTo(extended_width, 200, Easing.OutQuint); + panelColour = Color4Extensions.FromHex("ffd966"); + textColour = Color4Extensions.FromHex("2e576b"); + } + else + { + mainFillContainer.ResizeWidthTo(regular_width, 200, Easing.OutQuint); + panelColour = Color4Extensions.FromHex("3399cc"); + textColour = Color4.White; + } + } + + private Color4 panelColour { - get => totalScore; set { - totalScore = value; - scoreText.Text = totalScore.ToString("N0"); - - OnScoreChange?.Invoke(); + mainFillContainer.FadeColour(value, 200, Easing.OutQuint); + centralFill.FadeColour(value, 200, Easing.OutQuint); } } - private User user; - - public User User + private Color4 textColour { - get => user; set { - user = value; - userString.Text = user?.Username; + scoreText.FadeColour(value, 200, Easing.OutQuint); + accuracyText.FadeColour(value, 200, Easing.OutQuint); + comboText.FadeColour(value, 200, Easing.OutQuint); + usernameText.FadeColour(value, 200, Easing.OutQuint); + positionText.FadeColour(value, 200, Easing.OutQuint); } } - public GameplayLeaderboardScore() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChild = new Container - { - Masking = true, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Right = 2.5f }, - Spacing = new Vector2(2.5f), - Children = new[] - { - positionText = new OsuSpriteText - { - Alpha = 0, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), - }, - positionSymbol = new OsuSpriteText - { - Alpha = 0, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), - Text = ">", - }, - } - }, - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = 2.5f }, - Spacing = new Vector2(2.5f), - Children = new Drawable[] - { - userString = new OsuSpriteText - { - Size = new Vector2(80, 16), - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - }, - scoreText = new GlowingSpriteText - { - GlowColour = Color4Extensions.FromHex(@"83ccfa"), - Font = OsuFont.Numeric.With(size: 14), - } - } - }, - }, - }; - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - positionText.Colour = colours.YellowLight; - positionSymbol.Colour = colours.Yellow; + const float panel_shear = 0.15f; + const float shear_width = panel_height * panel_shear; + + InternalChildren = new Drawable[] + { + mainFillContainer = new Container + { + Width = regular_width, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Masking = true, + CornerRadius = 5f, + Shear = new Vector2(panel_shear, 0f), + Child = new Box + { + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + } + }, + new GridContainer + { + Width = regular_width, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 35f), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 85f), + }, + Content = new[] + { + new Drawable[] + { + positionText = new OsuSpriteText + { + Padding = new MarginPadding { Right = shear_width / 2 }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold), + Shadow = false, + }, + new Container + { + Padding = new MarginPadding { Horizontal = shear_width / 3 }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + Masking = true, + CornerRadius = 5f, + Shear = new Vector2(panel_shear, 0f), + RelativeSizeAxes = Axes.Both, + Children = new[] + { + centralFill = new Box + { + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("3399cc"), + }, + } + }, + usernameText = new OsuSpriteText + { + Padding = new MarginPadding { Left = shear_width }, + RelativeSizeAxes = Axes.X, + Width = 0.8f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + Text = User.Username, + Truncate = true, + Shadow = false, + } + } + }, + new Container + { + Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.White, + Children = new Drawable[] + { + scoreText = new OsuSpriteText + { + Spacing = new Vector2(-1f, 0f), + Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true), + Shadow = false, + }, + accuracyText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), + Spacing = new Vector2(-1f, 0f), + Shadow = false, + }, + comboText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Spacing = new Vector2(-1f, 0f), + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), + Shadow = false, + }, + }, + } + } + } + } + }; + + TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); + Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); + Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); } } }