From c71f3dee28357ce76c7a2e529190bf47b19dd741 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 18 Apr 2025 05:30:08 -0400 Subject: [PATCH] Update beatmap leaderboard score design to match new metrics --- ...cs => TestSceneBeatmapLeaderboardScore.cs} | 76 +++++--- .../SelectV2/BeatmapLeaderboardScore.cs | 180 ++++++++++++------ 2 files changed, 171 insertions(+), 85 deletions(-) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneLeaderboardScore.cs => TestSceneBeatmapLeaderboardScore.cs} (80%) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs similarity index 80% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs index b59a31c173..c82f20a758 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; @@ -28,7 +29,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneLeaderboardScore : SongSelectComponentsTestScene + public partial class TestSceneBeatmapLeaderboardScore : SongSelectComponentsTestScene { [Cached] private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -44,18 +45,23 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("create content", () => { - Children = new Drawable[] + Child = new PopoverContainer { - fillFlow = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 2f), - Shear = OsuGame.SHEAR, - }, - drawWidthText = new OsuSpriteText(), + fillFlow = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 2f), + Shear = OsuGame.SHEAR, + }, + drawWidthText = new OsuSpriteText(), + } }; foreach (var scoreInfo in getTestScores()) @@ -78,17 +84,22 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("create content", () => { - Children = new Drawable[] + Child = new PopoverContainer { - fillFlow = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 2f), - }, - drawWidthText = new OsuSpriteText(), + fillFlow = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 2f), + }, + drawWidthText = new OsuSpriteText(), + } }; foreach (var scoreInfo in getTestScores()) @@ -112,18 +123,23 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddStep("create content", () => { - Children = new Drawable[] + Child = new PopoverContainer { - fillFlow = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 2f), - Shear = OsuGame.SHEAR, - }, - drawWidthText = new OsuSpriteText(), + fillFlow = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 2f), + Shear = OsuGame.SHEAR, + }, + drawWidthText = new OsuSpriteText(), + } }; var scoreInfo = new ScoreInfo diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index c9413a9414..cefb3aec54 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -24,6 +25,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; @@ -57,17 +59,18 @@ namespace osu.Game.Screens.SelectV2 public int? Rank { get; init; } public bool IsPersonalBest { get; init; } - private const float expanded_right_content_width = 210; - private const float grade_width = 40; - private const float username_min_width = 125; - private const float statistics_regular_min_width = 175; - private const float statistics_compact_min_width = 100; - private const float rank_label_width = 65; + private const float expanded_right_content_width = 200; + private const float grade_width = 35; + private const float username_min_width = 120; + private const float statistics_regular_min_width = 165; + private const float statistics_compact_min_width = 90; + private const float rank_label_width = 60; private readonly ScoreInfo score; private readonly bool sheared; - private const int height = 60; + public const int HEIGHT = 50; + private const int corner_radius = 10; private const int transition_duration = 200; @@ -75,6 +78,10 @@ namespace osu.Game.Screens.SelectV2 private Colour4 backgroundColour; private ColourInfo totalScoreBackgroundGradient; + private static readonly Color4 personal_best_gradient_left = Color4Extensions.FromHex("#66FFCC"); + private static readonly Color4 personal_best_gradient_right = Color4Extensions.FromHex("#51A388"); + private ColourInfo personalBestGradient; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -111,7 +118,8 @@ namespace osu.Game.Screens.SelectV2 private Box totalScoreBackground = null!; private FillFlowContainer statisticsContainer = null!; - private RankLabel rankLabel = null!; + private Container personalBestIndicator = null!; + private Container rankLabelStandalone = null!; private Container rankLabelOverlay = null!; public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); @@ -124,7 +132,7 @@ namespace osu.Game.Screens.SelectV2 Shear = sheared ? OsuGame.SHEAR : Vector2.Zero; RelativeSizeAxes = Axes.X; - Height = height; + Height = HEIGHT; } [BackgroundDependencyLoader] @@ -132,9 +140,10 @@ namespace osu.Game.Screens.SelectV2 { var user = score.User; - foregroundColour = IsPersonalBest ? colourProvider.Background1 : colourProvider.Background5; - backgroundColour = IsPersonalBest ? colourProvider.Background2 : colourProvider.Background4; + foregroundColour = colourProvider.Background5; + backgroundColour = colourProvider.Background3; totalScoreBackgroundGradient = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), backgroundColour); + personalBestGradient = ColourInfo.GradientHorizontal(personal_best_gradient_left, personal_best_gradient_right); statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s, score) { @@ -167,14 +176,24 @@ namespace osu.Game.Screens.SelectV2 { new Drawable[] { - new Container + rankLabelStandalone = new Container { - AutoSizeAxes = Axes.X, + Width = rank_label_width, RelativeSizeAxes = Axes.Y, - Child = rankLabel = new RankLabel(Rank, sheared) + Children = new Drawable[] { - Width = rank_label_width, - RelativeSizeAxes = Axes.Y, + personalBestIndicator = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = -10f }, + Alpha = IsPersonalBest ? 1 : 0, + Colour = personalBestGradient, + Child = new Box { RelativeSizeAxes = Axes.Both }, + }, + new RankLabel(Rank, sheared, darkText: IsPersonalBest) + { + RelativeSizeAxes = Axes.Both, + } }, }, createCentreContent(user), @@ -203,7 +222,7 @@ namespace osu.Game.Screens.SelectV2 switch (s.NewValue) { case ScoringMode.Standardised: - rightContent.Width = 180f; + rightContent.Width = 170; break; case ScoringMode.Classic: @@ -224,15 +243,15 @@ namespace osu.Game.Screens.SelectV2 modsContainer.Padding = new MarginPadding { Top = 4f }; modsContainer.ChildrenEnumerable = score.Mods.AsOrdered().Take(Math.Min(maxMods, score.Mods.Length)).Select(mod => new ColouredModSwitchTiny(mod) { - Scale = new Vector2(0.375f) + Scale = new Vector2(0.3125f) }); if (score.Mods.Length > maxMods) { modsContainer.Remove(modsContainer[^1], true); - modsContainer.Add(new MoreModSwitchTiny(score.Mods.Length - maxMods + 1) + modsContainer.Add(new MoreModSwitchTiny(score.Mods) { - Scale = new Vector2(0.375f), + Scale = new Vector2(0.3125f), }); } } @@ -291,7 +310,7 @@ namespace osu.Game.Screens.SelectV2 }) { RelativeSizeAxes = Axes.None, - Size = new Vector2(height) + Size = new Vector2(HEIGHT) }, rankLabelOverlay = new Container { @@ -304,7 +323,7 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.Both, Colour = Colour4.Black.Opacity(0.5f), }, - new RankLabel(Rank, sheared) + new RankLabel(Rank, sheared, false) { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -337,18 +356,19 @@ namespace osu.Game.Screens.SelectV2 { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(24, 16), + Size = new Vector2(20, 14), }, new UpdateableTeamFlag(user.Team) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(40, 20), + Size = new Vector2(30, 15), }, new DateLabel(score.Date) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Colour = colourProvider.Content2, UseFullGlyphHeight = false, } } @@ -358,7 +378,7 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Text = user.Username, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold) + Font = OsuFont.Style.Heading2, } } }, @@ -441,7 +461,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.Centre, Spacing = new Vector2(-2), Colour = DrawableRank.GetRankNameColour(score.Rank), - Font = OsuFont.Numeric.With(size: 16), + Font = OsuFont.Numeric.With(size: 14), Text = DrawableRank.GetRankName(score.Rank), ShadowColour = Color4.Black.Opacity(0.3f), ShadowOffset = new Vector2(0, 0.08f), @@ -490,16 +510,21 @@ namespace osu.Game.Screens.SelectV2 UseFullGlyphHeight = false, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Current = scoreManager.GetBindableTotalScoreString(score), - Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), + Spacing = new Vector2(-1.5f), + Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), }, - modsContainer = new FillFlowContainer + new InputBlockingContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0f), + Child = modsContainer = new FillFlowContainer + { + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + }, }, } } @@ -523,9 +548,9 @@ namespace osu.Game.Screens.SelectV2 Alpha = 0; - content.MoveToY(75); - avatar.MoveToX(75); - nameLabel.MoveToX(150); + content.MoveToY(60); + avatar.MoveToX(60); + nameLabel.MoveToX(125); this.FadeIn(200); content.MoveToY(0, 800, Easing.OutQuint); @@ -568,10 +593,12 @@ namespace osu.Game.Screens.SelectV2 private void updateState() { var lightenedGradient = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0).Lighten(0.2f), backgroundColour.Lighten(0.2f)); + var personalBestLightenedGradient = ColourInfo.GradientHorizontal(personal_best_gradient_left.Lighten(0.2f), personal_best_gradient_right.Lighten(0.2f)); foreground.FadeColour(IsHovered ? foregroundColour.Lighten(0.2f) : foregroundColour, transition_duration, Easing.OutQuint); background.FadeColour(IsHovered ? backgroundColour.Lighten(0.2f) : backgroundColour, transition_duration, Easing.OutQuint); totalScoreBackground.FadeColour(IsHovered ? lightenedGradient : totalScoreBackgroundGradient, transition_duration, Easing.OutQuint); + personalBestIndicator.FadeColour(IsHovered ? personalBestLightenedGradient : personalBestGradient, transition_duration, Easing.OutQuint); if (IsHovered && currentMode != DisplayMode.Full) rankLabelOverlay.FadeIn(transition_duration, Easing.OutQuint); @@ -590,9 +617,9 @@ namespace osu.Game.Screens.SelectV2 if (currentMode != mode) { if (mode >= DisplayMode.Full) - rankLabel.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); + rankLabelStandalone.FadeIn(transition_duration, Easing.OutQuint).ResizeWidthTo(rank_label_width, transition_duration, Easing.OutQuint); else - rankLabel.FadeOut(transition_duration, Easing.OutQuint).MoveToX(-rankLabel.DrawWidth, transition_duration, Easing.OutQuint); + rankLabelStandalone.FadeOut(transition_duration, Easing.OutQuint).ResizeWidthTo(0, transition_duration, Easing.OutQuint); if (mode >= DisplayMode.Regular) { @@ -615,13 +642,13 @@ namespace osu.Game.Screens.SelectV2 private DisplayMode getCurrentDisplayMode() { - if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width + rank_label_width) + if (DrawWidth >= HEIGHT + username_min_width + statistics_regular_min_width + expanded_right_content_width + rank_label_width) return DisplayMode.Full; - if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width) + if (DrawWidth >= HEIGHT + username_min_width + statistics_regular_min_width + expanded_right_content_width) return DisplayMode.Regular; - if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width) + if (DrawWidth >= HEIGHT + username_min_width + statistics_compact_min_width + expanded_right_content_width) return DisplayMode.Compact; return DisplayMode.Minimal; @@ -642,7 +669,7 @@ namespace osu.Game.Screens.SelectV2 public DateLabel(DateTimeOffset date) : base(date) { - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Medium, italics: true); + Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold); } protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); @@ -677,7 +704,7 @@ namespace osu.Game.Screens.SelectV2 { Colour = colourProvider.Content2, Text = statisticInfo.Name, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold), }, value = new OsuSpriteText { @@ -685,7 +712,7 @@ namespace osu.Game.Screens.SelectV2 // since the accuracy is sometimes longer than its name. BypassAutoSizeAxes = Axes.X, Text = statisticInfo.Value, - Font = OsuFont.GetFont(size: 19, weight: FontWeight.Medium), + Font = OsuFont.Style.Body, } } }; @@ -697,21 +724,32 @@ namespace osu.Game.Screens.SelectV2 private partial class RankLabel : Container, IHasTooltip { - public RankLabel(int? rank, bool sheared) + private readonly bool darkText; + private readonly OsuSpriteText text; + + public RankLabel(int? rank, bool sheared, bool darkText) { + this.darkText = darkText; if (rank >= 1000) TooltipText = $"#{rank:N0}"; - Child = new OsuSpriteText + Child = text = new OsuSpriteText { Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold, italics: true), - Text = rank == null ? "-" : rank.Value.FormatRank().Insert(0, "#") + Font = OsuFont.Style.Heading2, + Text = rank == null ? "-" : rank.Value.FormatRank().Insert(0, "#"), + Shadow = !darkText, }; } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + text.Colour = darkText ? colourProvider.Background3 : colourProvider.Content1; + } + public LocalisableString TooltipText { get; } } @@ -732,17 +770,17 @@ namespace osu.Game.Screens.SelectV2 public ITooltip GetCustomTooltip() => new ModTooltip(colourProvider); } - private sealed partial class MoreModSwitchTiny : CompositeDrawable + private sealed partial class MoreModSwitchTiny : CompositeDrawable, IHasPopover { - private readonly int count; + private readonly IReadOnlyList mods; - public MoreModSwitchTiny(int count) + public MoreModSwitchTiny(IReadOnlyList mods) { - this.count = count; + this.mods = mods; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { Size = new Vector2(ModSwitchTiny.WIDTH, ModSwitchTiny.DEFAULT_HEIGHT); @@ -755,16 +793,17 @@ namespace osu.Game.Screens.SelectV2 new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f), + Colour = colourProvider.Background6, }, new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Shadow = false, - Font = OsuFont.Numeric.With(size: 24, weight: FontWeight.Black), - Text = $"+{count}", - Colour = colours.Yellow, + Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Bold), + Text = ". . .", + Colour = Color4.White, + UseFullGlyphHeight = false, Margin = new MarginPadding { Top = 4 @@ -773,6 +812,37 @@ namespace osu.Game.Screens.SelectV2 } }; } + + protected override bool OnClick(ClickEvent e) + { + this.ShowPopover(); + return true; + } + + protected override bool OnHover(HoverEvent e) => true; + + public Popover GetPopover() => new MoreModsPopover(mods); + } + + public partial class MoreModsPopover : OsuPopover + { + public MoreModsPopover(IReadOnlyList mods) + { + AutoSizeAxes = Axes.Both; + AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; + + Child = new FillFlowContainer + { + Width = 125f, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Spacing = new Vector2(2.5f), + ChildrenEnumerable = mods.AsOrdered().Select(m => new ColouredModSwitchTiny(m) + { + Scale = new Vector2(0.3125f), + }) + }; + } } #endregion