From c71f3dee28357ce76c7a2e529190bf47b19dd741 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 18 Apr 2025 05:30:08 -0400 Subject: [PATCH 01/21] 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 From 60171f1bf1da81c42cda2537f20f341a774d4a52 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 18 Apr 2025 05:31:29 -0400 Subject: [PATCH 02/21] Add new beatmap leaderboard score tooltip --- .../SelectV2/BeatmapLeaderboardScore.cs | 2 +- .../BeatmapLeaderboardScore_Tooltip.cs | 378 ++++++++++++++++++ 2 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index cefb3aec54..add5e39cf2 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.SelectV2 private Container rankLabelStandalone = null!; private Container rankLabelOverlay = null!; - public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); + public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(colourProvider); public virtual ScoreInfo TooltipContent => score; public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs new file mode 100644 index 0000000000..7f1997522e --- /dev/null +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs @@ -0,0 +1,378 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Overlays; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Utils; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class BeatmapLeaderboardScore + { + public partial class LeaderboardScoreTooltip : VisibilityContainer, ITooltip + { + private const float spacing = 20f; + + private DateAndStatisticsPanel dateAndStatistics = null!; + private ModsPanel modsPanel = null!; + private TotalScoreRankPanel totalScoreRankPanel = null!; + + [Cached] + private readonly OverlayColourProvider colourProvider; + + public LeaderboardScoreTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + } + + [BackgroundDependencyLoader] + private void load() + { + Width = 170; + AutoSizeAxes = Axes.Y; + + InternalChild = new ReverseChildIDFillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, -spacing), + Children = new Drawable[] + { + dateAndStatistics = new DateAndStatisticsPanel(), + modsPanel = new ModsPanel(), + totalScoreRankPanel = new TotalScoreRankPanel(), + }, + }; + } + + private ScoreInfo? lastContent; + + public void SetContent(ScoreInfo content) + { + if (lastContent != null && lastContent.Equals(content)) + return; + + dateAndStatistics.Score = content; + modsPanel.Score = content; + totalScoreRankPanel.Score = content; + lastContent = content; + } + + protected override void PopIn() => this.FadeIn(300, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); + public void Move(Vector2 pos) => Position = pos; + + private partial class DateAndStatisticsPanel : CompositeDrawable + { + private OsuSpriteText absoluteDate = null!; + private DrawableDate relativeDate = null!; + private FillFlowContainer statistics = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public ScoreInfo Score + { + set + { + absoluteDate.Text = value.Date.ToLocalisableString(@"dd MMMM yyyy h:mm tt"); + relativeDate.Date = value.Date; + + var judgementsStatistics = value.GetStatisticsForDisplay().Select(s => + { + Colour4 colour = colours.ForHitResult(s.Result); + var hsl = colour.ToHSL(); + + Colour4 lightColour = Colour4.FromHSL(hsl.X, hsl.Y, 0.8f); + return new StatisticRow(s.DisplayName.ToUpper(), lightColour, s.Count.ToLocalisableString("N0")); + }); + + double multiplier = 1.0; + + foreach (var mod in value.Mods) + multiplier *= mod.ScoreMultiplier; + + var generalStatistics = new[] + { + new StatisticRow("Score Multiplier", colourProvider.Content2, ModUtils.FormatScoreMultiplier(multiplier)), + new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeadersCombo, colourProvider.Content2, value.MaxCombo.ToLocalisableString(@"0\x")), + new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, colourProvider.Content2, value.Accuracy.FormatAccuracy()), + }; + + if (value.PP != null) + { + generalStatistics = new[] + { + new StatisticRow(BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper(), colourProvider.Content2, value.PP.ToLocalisableString("N0")) + }.Concat(generalStatistics).ToArray(); + } + + statistics.ChildrenEnumerable = judgementsStatistics + .Append(Empty().With(d => d.Height = 20)) + .Concat(generalStatistics); + } + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + CornerRadius = corner_radius; + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4f, + }; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 4f), + Margin = new MarginPadding { Top = 8f }, + Children = new Drawable[] + { + absoluteDate = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), + UseFullGlyphHeight = false, + }, + relativeDate = new DrawableDate(default, OsuFont.Style.Caption1.Size) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = colourProvider.Content2, + UseFullGlyphHeight = false, + }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CornerRadius = corner_radius, + Masking = true, + Margin = new MarginPadding { Top = 4f }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3, + }, + statistics = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 4f), + Padding = new MarginPadding(8f), + }, + }, + }, + }, + }, + }; + } + } + + private partial class StatisticRow : CompositeDrawable + { + public StatisticRow(LocalisableString label, Color4 labelColour, LocalisableString value) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new[] + { + new OsuSpriteText + { + Text = label, + Colour = labelColour, + Font = OsuFont.Style.Caption2.With(weight: FontWeight.SemiBold), + }, + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = value, + Colour = Color4.White, + Font = OsuFont.Style.Caption2, + }, + }; + } + } + + private partial class ModsPanel : CompositeDrawable + { + private FillFlowContainer modsFlow = null!; + + public ScoreInfo Score + { + set + { + var mods = value.Mods; + + if (!mods.Any()) + Hide(); + else + { + Show(); + + modsFlow.ChildrenEnumerable = mods.AsOrdered().Select(m => new ModSwitchTiny(m) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.3125f), + Active = { Value = true }, + }); + } + } + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + CornerRadius = corner_radius; + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4f, + }; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Transparent, + }, + modsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 6f, Top = 6f + spacing }, + Padding = new MarginPadding { Horizontal = 16f }, + Spacing = new Vector2(2.5f), + }, + }; + } + } + + public partial class TotalScoreRankPanel : CompositeDrawable + { + private Box rankBackground = null!; + private Container rankContainer = null!; + private OsuSpriteText totalScore = null!; + + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + + public ScoreInfo Score + { + set + { + rankBackground.Colour = ColourInfo.GradientVertical( + OsuColour.ForRank(value.Rank).Opacity(0f), + OsuColour.ForRank(value.Rank).Opacity(0.5f)); + rankContainer.Child = new DrawableRank(value.Rank); + totalScore.Current = scoreManager.GetBindableTotalScoreString(value); + } + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + CornerRadius = corner_radius; + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Radius = 4f, + }; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#353535"), + }, + rankBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + rankContainer = new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(25f, 14f), + Margin = new MarginPadding { Bottom = 5f }, + }, + totalScore = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding { Bottom = 25f, Top = 10f + spacing }, + Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), + Spacing = new Vector2(-1.5f), + UseFullGlyphHeight = false, + }, + }; + } + } + } + } +} From 34e8943c74198220a626dc1fe3775e7904eb1cfb Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 18 Apr 2025 05:42:18 -0400 Subject: [PATCH 03/21] Add beatmap leaderboard wedge --- .../TestSceneBeatmapLeaderboardWedge.cs | 352 +++++++++++++++++ .../SelectV2/BeatmapLeaderboardWedge.cs | 370 ++++++++++++++++++ 2 files changed, 722 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs create mode 100644 osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs new file mode 100644 index 0000000000..060f2ad956 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs @@ -0,0 +1,352 @@ +// 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.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Screens.SelectV2; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.SongSelect; +using osu.Game.Users; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneBeatmapLeaderboardWedge : SongSelectComponentsTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + private TestBeatmapLeaderboardWedge leaderboard = null!; + private ScoreManager scoreManager = null!; + private RulesetStore rulesetStore = null!; + private BeatmapManager beatmapManager = null!; + private OsuContextMenuContainer contentContainer = null!; + private DialogOverlay dialogOverlay = null!; + + private LeaderboardManager leaderboardManager = null!; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API)); + dependencies.Cache(leaderboardManager = new LeaderboardManager()); + + Dependencies.Cache(Realm); + + return dependencies; + } + + [BackgroundDependencyLoader] + private void load() + { + LoadComponent(dialogOverlay = new DialogOverlay + { + Depth = -1 + }); + + LoadComponent(leaderboardManager); + + Child = contentContainer = new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.X, + Height = 500, + Children = new Drawable[] + { + dialogOverlay, + } + }; + + AddSliderStep("change relative height", 0f, 1f, 0.65f, v => Schedule(() => + { + contentContainer.Height = v * DrawHeight; + })); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + if (leaderboard.IsNotNull()) + contentContainer.Remove(leaderboard, false); + + contentContainer.Add(leaderboard = new TestBeatmapLeaderboardWedge + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + }); + }); + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + } + + [Test] + public void TestGlobalScoresDisplay() + { + setScope(BeatmapLeaderboardScope.Global); + + AddStep(@"New Scores", () => leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()))); + AddStep(@"New Scores with teams", () => leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()).Select(s => + { + s.User.Team = new APITeam(); + return s; + }))); + } + + [Test] + public void TestPersonalBest() + { + AddStep(@"Show personal best", showPersonalBest); + } + + [Test] + public void TestPersonalBestWithNullPosition() + { + AddStep("null personal best position", showPersonalBestWithNullPosition); + } + + [Test] + public void TestPlaceholderStates() + { + AddStep("ensure no scores displayed", () => leaderboard.SetScores(Array.Empty())); + + AddStep(@"Network failure", () => leaderboard.SetState(LeaderboardState.NetworkFailure)); + AddStep(@"No team", () => leaderboard.SetState(LeaderboardState.NoTeam)); + AddStep(@"No supporter", () => leaderboard.SetState(LeaderboardState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetState(LeaderboardState.NotLoggedIn)); + AddStep(@"Ruleset unavailable", () => leaderboard.SetState(LeaderboardState.RulesetUnavailable)); + AddStep(@"Beatmap unavailable", () => leaderboard.SetState(LeaderboardState.BeatmapUnavailable)); + AddStep(@"None selected", () => leaderboard.SetState(LeaderboardState.NoneSelected)); + } + + [Test] + public void TestUseTheseModsDoesNotCopySystemMods() + { + AddStep(@"set scores", () => leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()), new ScoreInfo + { + OnlineID = 1337, + Position = 999, + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Ruleset = new OsuRuleset().RulesetInfo, + Mods = new Mod[] { new OsuModHidden(), new ModScoreV2(), }, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + CountryCode = CountryCode.ES, + } + })); + AddUntilStep("wait for scores", () => this.ChildrenOfType().Count(), () => Is.GreaterThan(0)); + AddStep("right click panel", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Last()); + InputManager.Click(MouseButton.Right); + }); + AddStep("click use these mods", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("received HD", () => this.ChildrenOfType().Last().SelectedMods.Value.Any(m => m is OsuModHidden)); + AddAssert("did not receive SV2", () => !this.ChildrenOfType().Last().SelectedMods.Value.Any(m => m is ModScoreV2)); + } + + [Test] + [Ignore("Pending implementation")] + // todo: add score fetch functionality to BeatmapLeaderboardWedge + public void TestLocalScoresDisplay() + { + BeatmapInfo beatmapInfo = null!; + + setScope(BeatmapLeaderboardScope.Local); + + AddStep(@"Set beatmap", () => + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + }); + + clearScores(); + checkDisplayedCount(0); + + importMoreScores(() => beatmapInfo); + checkDisplayedCount(10); + + importMoreScores(() => beatmapInfo); + checkDisplayedCount(20); + + clearScores(); + checkDisplayedCount(0); + } + + [Test] + [Ignore("Pending implementation")] + // todo: add score fetch functionality to BeatmapLeaderboardWedge + public void TestLocalScoresDisplayOnBeatmapEdit() + { + BeatmapInfo beatmapInfo = null!; + string originalHash = string.Empty; + + setScope(BeatmapLeaderboardScope.Local); + + AddStep(@"Import beatmap", () => + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + }); + + clearScores(); + checkDisplayedCount(0); + + AddStep(@"Perform initial save to guarantee stable hash", () => + { + IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap; + beatmapManager.Save(beatmapInfo, beatmap); + + originalHash = beatmapInfo.Hash; + }); + + importMoreScores(() => beatmapInfo); + + checkDisplayedCount(10); + checkStoredCount(10); + + AddStep(@"Save with changes", () => + { + IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap; + beatmap.Difficulty.ApproachRate = 12; + beatmapManager.Save(beatmapInfo, beatmap); + }); + + AddAssert("Hash changed", () => beatmapInfo.Hash, () => Is.Not.EqualTo(originalHash)); + checkDisplayedCount(0); + checkStoredCount(10); + + importMoreScores(() => beatmapInfo); + importMoreScores(() => beatmapInfo); + checkDisplayedCount(20); + checkStoredCount(30); + + AddStep(@"Revert changes", () => + { + IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap; + beatmap.Difficulty.ApproachRate = 8; + beatmapManager.Save(beatmapInfo, beatmap); + }); + + AddAssert("Hash restored", () => beatmapInfo.Hash, () => Is.EqualTo(originalHash)); + checkDisplayedCount(10); + checkStoredCount(30); + + clearScores(); + checkDisplayedCount(0); + checkStoredCount(0); + } + + private void showPersonalBestWithNullPosition() + { + leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()), new ScoreInfo + { + OnlineID = 1337, + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + Ruleset = new OsuRuleset().RulesetInfo, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + CountryCode = CountryCode.ES, + }, + }); + } + + private void showPersonalBest() + { + leaderboard.SetScores(TestSceneBeatmapLeaderboard.GenerateSampleScores(new BeatmapInfo()), new ScoreInfo + { + OnlineID = 1337, + Position = 999, + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Ruleset = new OsuRuleset().RulesetInfo, + Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() }, + User = new APIUser + { + Id = 6602580, + Username = @"waaiiru", + CountryCode = CountryCode.ES, + } + }); + } + + private void setScope(BeatmapLeaderboardScope scope) + { + AddStep(@"Set scope", () => ((Bindable)leaderboard.Scope).Value = scope); + } + + private void importMoreScores(Func beatmapInfo) + { + AddStep(@"Import new scores", () => + { + foreach (var score in TestSceneBeatmapLeaderboard.GenerateSampleScores(beatmapInfo())) + scoreManager.Import(score); + }); + } + + private void clearScores() + { + AddStep("Clear all scores", () => scoreManager.Delete()); + } + + private void checkDisplayedCount(int expected) => + AddUntilStep($"{expected} scores displayed", () => leaderboard.ChildrenOfType().Count(), () => Is.EqualTo(expected)); + + private void checkStoredCount(int expected) => + AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All().Count(s => !s.DeletePending)), () => Is.EqualTo(expected)); + + private partial class TestBeatmapLeaderboardWedge : BeatmapLeaderboardWedge + { + public new void SetState(LeaderboardState state) => base.SetState(state); + public new void SetScores(IEnumerable scores, ScoreInfo? userScore = null) => base.SetScores(scores, userScore); + } + } +} diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs new file mode 100644 index 0000000000..d15927a67f --- /dev/null +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -0,0 +1,370 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Extensions; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osu.Game.Online.API; +using osu.Game.Online.Leaderboards; +using osu.Game.Online.Placeholders; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Screens.Select.Leaderboards; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class BeatmapLeaderboardWedge : VisibilityContainer + { + private Container scoresContainer = null!; + + private OsuScrollContainer scoresScroll = null!; + private Container personalBestDisplay = null!; + private Container personalBestScoreContainer = null!; + private LoadingLayer loading = null!; + + private Container placeholderContainer = null!; + private Placeholder? placeholder; + + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IBindable> mods { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public IBindable Scope { get; } = new Bindable(); + + private bool isOnlineScope => Scope.Value != BeatmapLeaderboardScope.Local; + + public IBindable FilterBySelectedMods { get; } = new BindableBool(); + + private CancellationTokenSource? cancellationTokenSource; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + Child = new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + scoresScroll = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Shear = OsuGame.SHEAR, + Child = scoresContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 4f, Bottom = 180f }, + }, + }, + personalBestDisplay = new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Shear = OsuGame.SHEAR, + Margin = new MarginPadding { Left = -60f }, + CornerRadius = 10f, + Masking = true, + // push the personal best 1px down to hide masking issues + Y = 1f, + X = -100f, + Alpha = 0f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Shear = -OsuGame.SHEAR, + Padding = new MarginPadding { Top = 5f, Bottom = 30f, Left = 100f, Right = 30f }, + Children = new Drawable[] + { + new OsuSpriteText + { + Colour = colourProvider.Content2, + Text = "Personal Best", + Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), + }, + personalBestScoreContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 20f }, + }, + } + }, + }, + }, + placeholderContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + loading = new LoadingLayer(), + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Scope.BindValueChanged(_ => refetchScores()); + FilterBySelectedMods.BindValueChanged(_ => refetchScores()); + beatmap.BindValueChanged(_ => refetchScores()); + ruleset.BindValueChanged(_ => refetchScores()); + mods.BindValueChanged(_ => refetchScoresFromMods()); + + refetchScores(); + } + + protected override void PopIn() + { + this.FadeIn(300, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(300, Easing.OutQuint); + } + + private void refetchScoresFromMods() + { + if (FilterBySelectedMods.Value) + refetchScores(); + } + + private void refetchScores() + { + SetScores(Array.Empty(), null); + SetState(LeaderboardState.Retrieving); + + if (beatmap.IsDefault) + { + SetState(LeaderboardState.NoneSelected); + return; + } + + var fetchBeatmapInfo = beatmap.Value.BeatmapInfo; + var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; + + if (!api.IsLoggedIn) + { + SetState(LeaderboardState.NotLoggedIn); + return; + } + + if (!fetchRuleset.IsLegacyRuleset()) + { + SetState(LeaderboardState.RulesetUnavailable); + return; + } + + if ((fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) && isOnlineScope) + { + SetState(LeaderboardState.BeatmapUnavailable); + return; + } + + if (Scope.Value.RequiresSupporter(FilterBySelectedMods.Value) && !api.LocalUser.Value.IsSupporter) + { + SetState(LeaderboardState.NotSupporter); + return; + } + + if (Scope.Value == BeatmapLeaderboardScope.Team && api.LocalUser.Value.Team == null) + { + SetState(LeaderboardState.NoTeam); + return; + } + + // todo: missing implementation + SetScores(Array.Empty(), null); + } + + protected void SetScores(IEnumerable scores, ScoreInfo? userScore) + { + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + + clearScores(); + SetState(LeaderboardState.Success); + + if (!scores.Any()) + { + SetState(LeaderboardState.NoScores); + return; + } + + LoadComponentsAsync(scores.Select((s, i) => new BeatmapLeaderboardScore(s) + { + Rank = i + 1, + IsPersonalBest = s.OnlineID == userScore?.OnlineID, + SelectedMods = { BindTarget = mods }, + }), loadedScores => + { + int delay = 100; + int accumulation = 1; + int i = 0; + + foreach (var scoreDrawable in loadedScores) + { + Container scoreDrawableContainer; + + scoresContainer.Add(scoreDrawableContainer = new Container + { + Shear = -OsuGame.SHEAR, + Y = (BeatmapLeaderboardScore.HEIGHT + 4f) * i, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0f, + Padding = new MarginPadding { Left = 80f }, + Child = scoreDrawable, + }); + + scoreDrawableContainer.Delay(delay).FadeIn(300, Easing.OutQuint); + scoreDrawableContainer.MoveToX(-100f).Delay(delay).MoveToX(0f, 300, Easing.OutQuint); + + delay += Math.Max(0, 50 - accumulation); + accumulation *= 2; + i++; + } + }, cancellation: cancellationTokenSource.Token); + + if (userScore != null) + { + personalBestDisplay.MoveToX(0, 600, Easing.OutQuint); + personalBestDisplay.FadeIn(600, Easing.OutQuint); + personalBestScoreContainer.Child = new BeatmapLeaderboardScore(userScore) + { + IsPersonalBest = true, + Rank = userScore.Position, + SelectedMods = { BindTarget = mods }, + }; + + scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding { Bottom = 100 }, 300, Easing.OutQuint); + } + } + + private void clearScores() + { + foreach (var scoreDrawable in scoresContainer) + { + scoreDrawable.MoveToX(-50f, 200, Easing.OutQuint); + scoreDrawable.FadeOut(200, Easing.OutQuint); + scoreDrawable.Expire(); + } + + personalBestDisplay.MoveToX(-100, 300, Easing.OutQuint); + personalBestDisplay.FadeOut(300, Easing.OutQuint); + scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding(), 300, Easing.OutQuint); + } + + private LeaderboardState displayedState; + + protected void SetState(LeaderboardState state) + { + if (state == displayedState) + return; + + if (state == LeaderboardState.Retrieving) + loading.Show(); + else + loading.Hide(); + + displayedState = state; + + placeholder?.FadeOut(150, Easing.OutQuint).Expire(); + placeholder = getPlaceholderFor(state); + + if (placeholder == null) + return; + + placeholderContainer.Child = placeholder; + + placeholder.ScaleTo(0.8f).Then().ScaleTo(1, 900, Easing.OutQuint); + placeholder.FadeInFromZero(300, Easing.OutQuint); + } + + private Placeholder? getPlaceholderFor(LeaderboardState state) + { + switch (state) + { + case LeaderboardState.NetworkFailure: + return new ClickablePlaceholder(LeaderboardStrings.CouldntFetchScores, FontAwesome.Solid.Sync) + { + Action = refetchScores + }; + + case LeaderboardState.NoneSelected: + return new MessagePlaceholder(LeaderboardStrings.PleaseSelectABeatmap); + + case LeaderboardState.RulesetUnavailable: + return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisRuleset); + + case LeaderboardState.BeatmapUnavailable: + return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisBeatmap); + + case LeaderboardState.NoScores: + return new MessagePlaceholder(LeaderboardStrings.NoRecordsYet); + + case LeaderboardState.NotLoggedIn: + return new LoginPlaceholder(LeaderboardStrings.PleaseSignInToViewOnlineLeaderboards); + + case LeaderboardState.NotSupporter: + return new MessagePlaceholder(LeaderboardStrings.PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard); + + case LeaderboardState.NoTeam: + return new MessagePlaceholder(LeaderboardStrings.NoTeam); + + case LeaderboardState.Retrieving: + return null; + + case LeaderboardState.Success: + return null; + + default: + throw new ArgumentOutOfRangeException(nameof(state)); + } + } + } +} From 81d54a9f32ad3f2eb04d360b6078ab78aa2f03ec Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 18 Apr 2025 05:43:47 -0400 Subject: [PATCH 04/21] Implement score fetch functionality Copied logic from `BeatmapLeaderboard`. --- .../TestSceneBeatmapLeaderboardWedge.cs | 4 --- .../SelectV2/BeatmapLeaderboardWedge.cs | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs index 060f2ad956..f034049476 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs @@ -182,8 +182,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 } [Test] - [Ignore("Pending implementation")] - // todo: add score fetch functionality to BeatmapLeaderboardWedge public void TestLocalScoresDisplay() { BeatmapInfo beatmapInfo = null!; @@ -212,8 +210,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 } [Test] - [Ignore("Pending implementation")] - // todo: add score fetch functionality to BeatmapLeaderboardWedge public void TestLocalScoresDisplayOnBeatmapEdit() { BeatmapInfo beatmapInfo = null!; diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index d15927a67f..66e799c93e 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -43,6 +42,9 @@ namespace osu.Game.Screens.SelectV2 private Container placeholderContainer = null!; private Placeholder? placeholder; + [Resolved] + private LeaderboardManager leaderboardManager { get; set; } = null!; + [Resolved] private IBindable beatmap { get; set; } = null!; @@ -66,6 +68,8 @@ namespace osu.Game.Screens.SelectV2 private CancellationTokenSource? cancellationTokenSource; + private readonly Bindable fetchedScores = new Bindable(); + [BackgroundDependencyLoader] private void load() { @@ -142,6 +146,8 @@ namespace osu.Game.Screens.SelectV2 loading = new LoadingLayer(), } }; + + ((IBindable)fetchedScores).BindTo(leaderboardManager.Scores); } protected override void LoadComplete() @@ -217,8 +223,22 @@ namespace osu.Game.Screens.SelectV2 return; } - // todo: missing implementation - SetScores(Array.Empty(), null); + leaderboardManager.FetchWithCriteriaAsync(new LeaderboardCriteria(fetchBeatmapInfo, fetchRuleset, Scope.Value, FilterBySelectedMods.Value ? mods.Value.ToArray() : null)) + .ContinueWith(t => + { + if (t.Exception != null && !t.IsCanceled) + { + Schedule(() => SetState(LeaderboardState.NetworkFailure)); + return; + } + + fetchedScores.UnbindEvents(); + fetchedScores.BindValueChanged(scores => + { + if (scores.NewValue != null) + Schedule(() => SetScores(scores.NewValue.TopScores, scores.NewValue.UserScore)); + }, true); + }); } protected void SetScores(IEnumerable scores, ScoreInfo? userScore) From 7a18a771b3e557e2f64a604636de2f70f99d4952 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Apr 2025 17:44:57 +0900 Subject: [PATCH 05/21] Fix regression from copy waste --- .../TestSceneBeatmapLeaderboardWedge.cs | 21 +++++++++++++++++++ .../SelectV2/BeatmapLeaderboardWedge.cs | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs index f034049476..baeb9ba5bb 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs @@ -209,6 +209,27 @@ namespace osu.Game.Tests.Visual.SongSelectV2 checkDisplayedCount(0); } + [Test] + public void TestLocalScoresDisplayWorksWhenStartingOffline() + { + BeatmapInfo beatmapInfo = null!; + + AddStep("Log out", () => API.Logout()); + setScope(BeatmapLeaderboardScope.Local); + + AddStep(@"Import beatmap", () => + { + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + }); + + clearScores(); + importMoreScores(() => beatmapInfo); + checkDisplayedCount(10); + } + [Test] public void TestLocalScoresDisplayOnBeatmapEdit() { diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index 66e799c93e..a6db5ec7a5 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -193,7 +193,7 @@ namespace osu.Game.Screens.SelectV2 var fetchBeatmapInfo = beatmap.Value.BeatmapInfo; var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; - if (!api.IsLoggedIn) + if (!api.IsLoggedIn && isOnlineScope) { SetState(LeaderboardState.NotLoggedIn); return; From bec2d62a7ab6dd793c2c320aac76172dd957e95f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Apr 2025 18:01:12 +0900 Subject: [PATCH 06/21] Seal `BeatmapLeaderboardScore` for now We'll need to figure out what to do in multiplayer cases in the future, but the hope is that it can be done without further subclassing if possible. --- .../SelectV2/BeatmapLeaderboardScore.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index add5e39cf2..197d13d30f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -45,7 +45,7 @@ using CommonStrings = osu.Game.Localisation.CommonStrings; namespace osu.Game.Screens.SelectV2 { - public partial class BeatmapLeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip + public sealed partial class BeatmapLeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { public Bindable> SelectedMods = new Bindable>(); @@ -66,7 +66,6 @@ namespace osu.Game.Screens.SelectV2 private const float statistics_compact_min_width = 90; private const float rank_label_width = 60; - private readonly ScoreInfo score; private readonly bool sheared; public const int HEIGHT = 50; @@ -109,7 +108,6 @@ namespace osu.Game.Screens.SelectV2 private Container rightContent = null!; - protected Container RankContainer { get; private set; } = null!; private FillFlowContainer flagBadgeAndDateContainer = null!; private FillFlowContainer modsContainer = null!; @@ -123,11 +121,12 @@ namespace osu.Game.Screens.SelectV2 private Container rankLabelOverlay = null!; public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(colourProvider); - public virtual ScoreInfo TooltipContent => score; + + public ScoreInfo TooltipContent { get; } public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true) { - this.score = score; + TooltipContent = score; this.sheared = sheared; Shear = sheared ? OsuGame.SHEAR : Vector2.Zero; @@ -138,14 +137,14 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - var user = score.User; + var user = TooltipContent.User; 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) + statisticsLabels = getStatistics(TooltipContent).Select(s => new ScoreComponentLabel(s, TooltipContent) { // ensure statistics container is the correct width when invalidating AlwaysPresent = true, @@ -238,18 +237,18 @@ namespace osu.Game.Screens.SelectV2 { int maxMods = scoringMode.Value == ScoringMode.Standardised ? 4 : 5; - if (score.Mods.Length > 0) + if (TooltipContent.Mods.Length > 0) { modsContainer.Padding = new MarginPadding { Top = 4f }; - modsContainer.ChildrenEnumerable = score.Mods.AsOrdered().Take(Math.Min(maxMods, score.Mods.Length)).Select(mod => new ColouredModSwitchTiny(mod) + modsContainer.ChildrenEnumerable = TooltipContent.Mods.AsOrdered().Take(Math.Min(maxMods, TooltipContent.Mods.Length)).Select(mod => new ColouredModSwitchTiny(mod) { Scale = new Vector2(0.3125f) }); - if (score.Mods.Length > maxMods) + if (TooltipContent.Mods.Length > maxMods) { modsContainer.Remove(modsContainer[^1], true); - modsContainer.Add(new MoreModSwitchTiny(score.Mods) + modsContainer.Add(new MoreModSwitchTiny(TooltipContent.Mods) { Scale = new Vector2(0.3125f), }); @@ -273,7 +272,7 @@ namespace osu.Game.Screens.SelectV2 new UserCoverBackground { RelativeSizeAxes = Axes.Both, - User = score.User, + User = TooltipContent.User, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -364,7 +363,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.CentreLeft, Size = new Vector2(30, 15), }, - new DateLabel(score.Date) + new DateLabel(TooltipContent.Date) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -428,7 +427,7 @@ namespace osu.Game.Screens.SelectV2 Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(TooltipContent.Rank)), }, }, new Box @@ -437,7 +436,7 @@ namespace osu.Game.Screens.SelectV2 Width = grade_width, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Colour = OsuColour.ForRank(score.Rank), + Colour = OsuColour.ForRank(TooltipContent.Rank), }, new TrianglesV2 { @@ -446,9 +445,9 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.TopRight, SpawnRatio = 2, Velocity = 0.7f, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Darken(0.2f)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(TooltipContent.Rank).Darken(0.2f)), }, - RankContainer = new Container + new Container { Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Anchor = Anchor.CentreRight, @@ -460,9 +459,9 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.Centre, Origin = Anchor.Centre, Spacing = new Vector2(-2), - Colour = DrawableRank.GetRankNameColour(score.Rank), + Colour = DrawableRank.GetRankNameColour(TooltipContent.Rank), Font = OsuFont.Numeric.With(size: 14), - Text = DrawableRank.GetRankName(score.Rank), + Text = DrawableRank.GetRankName(TooltipContent.Rank), ShadowColour = Color4.Black.Opacity(0.3f), ShadowOffset = new Vector2(0, 0.08f), Shadow = true, @@ -492,7 +491,7 @@ namespace osu.Game.Screens.SelectV2 new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Opacity(0.5f)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(TooltipContent.Rank).Opacity(0.5f)), }, new FillFlowContainer { @@ -509,7 +508,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.TopRight, UseFullGlyphHeight = false, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Current = scoreManager.GetBindableTotalScoreString(score), + Current = scoreManager.GetBindableTotalScoreString(TooltipContent), Spacing = new Vector2(-1.5f), Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), }, @@ -535,7 +534,7 @@ namespace osu.Game.Screens.SelectV2 }, }; - protected (CaseTransformableString, LocalisableString DisplayAccuracy)[] GetStatistics(ScoreInfo model) => new[] + private (CaseTransformableString, LocalisableString DisplayAccuracy)[] getStatistics(ScoreInfo model) => new[] { (BeatmapsetsStrings.ShowScoreboardHeadersCombo.ToUpper(), model.MaxCombo.ToString().Insert(model.MaxCombo.ToString().Length, "x")), (BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper(), model.DisplayAccuracy), @@ -854,18 +853,18 @@ namespace osu.Game.Screens.SelectV2 List items = new List(); // system mods should never be copied across regardless of anything. - var copyableMods = score.Mods.Where(m => IsValidMod.Invoke(m) && m.Type != ModType.System).ToArray(); + var copyableMods = TooltipContent.Mods.Where(m => IsValidMod.Invoke(m) && m.Type != ModType.System).ToArray(); if (copyableMods.Length > 0) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = copyableMods)); - if (score.OnlineID > 0) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{score.OnlineID}"))); + if (TooltipContent.OnlineID > 0) + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{TooltipContent.OnlineID}"))); - if (score.Files.Count <= 0) return items.ToArray(); + if (TooltipContent.Files.Count <= 0) return items.ToArray(); - items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); - items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(TooltipContent))); + items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(TooltipContent)))); return items.ToArray(); } From 26f2703688258f010f31699dd2052584b67a2225 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Apr 2025 18:04:41 +0900 Subject: [PATCH 07/21] Fix non-sheared test showing sheared drawables --- .../Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs index c82f20a758..c2f1eb6b15 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 foreach (var scoreInfo in getTestScores()) { - fillFlow.Add(new BeatmapLeaderboardScore(scoreInfo) + fillFlow.Add(new BeatmapLeaderboardScore(scoreInfo, sheared: false) { Rank = scoreInfo.Position, IsPersonalBest = scoreInfo.User.Id == 2, From de821005dc5a82ec23c33c0f3d3a5a12453b65ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Apr 2025 18:30:05 +0900 Subject: [PATCH 08/21] Make leaderbaord animation barely bareable --- .../TestSceneBeatmapLeaderboardWedge.cs | 12 +++--- .../SelectV2/BeatmapLeaderboardWedge.cs | 38 ++++++++++++------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs index baeb9ba5bb..f03d83b5e8 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs @@ -107,6 +107,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2 base.SetUpSteps(); } + [Test] + public void TestPersonalBest() + { + AddStep(@"Show personal best", showPersonalBest); + } + [Test] public void TestGlobalScoresDisplay() { @@ -120,12 +126,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 }))); } - [Test] - public void TestPersonalBest() - { - AddStep(@"Show personal best", showPersonalBest); - } - [Test] public void TestPersonalBestWithNullPosition() { diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index a6db5ec7a5..774c1540c7 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -262,15 +262,14 @@ namespace osu.Game.Screens.SelectV2 SelectedMods = { BindTarget = mods }, }), loadedScores => { - int delay = 100; - int accumulation = 1; + int delay = 200; int i = 0; - foreach (var scoreDrawable in loadedScores) + foreach (var d in loadedScores) { - Container scoreDrawableContainer; + Container animContainer; - scoresContainer.Add(scoreDrawableContainer = new Container + scoresContainer.Add(animContainer = new Container { Shear = -OsuGame.SHEAR, Y = (BeatmapLeaderboardScore.HEIGHT + 4f) * i, @@ -278,14 +277,16 @@ namespace osu.Game.Screens.SelectV2 AutoSizeAxes = Axes.Y, Alpha = 0f, Padding = new MarginPadding { Left = 80f }, - Child = scoreDrawable, + Child = d, }); - scoreDrawableContainer.Delay(delay).FadeIn(300, Easing.OutQuint); - scoreDrawableContainer.MoveToX(-100f).Delay(delay).MoveToX(0f, 300, Easing.OutQuint); + animContainer + .MoveToX(-20f) + .Delay(delay) + .FadeIn(300, Easing.OutQuint) + .MoveToX(0f, 300, Easing.OutQuint); - delay += Math.Max(0, 50 - accumulation); - accumulation *= 2; + delay += 30; i++; } }, cancellation: cancellationTokenSource.Token); @@ -307,11 +308,20 @@ namespace osu.Game.Screens.SelectV2 private void clearScores() { - foreach (var scoreDrawable in scoresContainer) + float delay = 0; + + foreach (var d in scoresContainer) { - scoreDrawable.MoveToX(-50f, 200, Easing.OutQuint); - scoreDrawable.FadeOut(200, Easing.OutQuint); - scoreDrawable.Expire(); + // Avoid applying animations a second time to drawables which are already fading out. + if (d.LifetimeEnd != double.MaxValue) + continue; + + d.Delay(delay) + .MoveToX(-10f, 120, Easing.Out) + .FadeOut(120, Easing.Out) + .Expire(); + + delay += 20; } personalBestDisplay.MoveToX(-100, 300, Easing.OutQuint); From 8bf95ca3a864702e061d407620794288e4814e24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Apr 2025 18:41:15 +0900 Subject: [PATCH 09/21] Don't animate on initial mode display This removes a lot of movement, but honestly it didn't feel good in the first place. If anything I'll come back with a second-pass animation pass on this. --- .../Screens/SelectV2/BeatmapLeaderboardScore.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index 197d13d30f..d76f2b181f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -398,8 +398,6 @@ namespace osu.Game.Screens.SelectV2 Direction = FillDirection.Horizontal, Children = statisticsLabels, Alpha = 0, - LayoutEasing = Easing.OutQuint, - LayoutDuration = transition_duration, } } } @@ -615,25 +613,26 @@ namespace osu.Game.Screens.SelectV2 if (currentMode != mode) { + double duration = currentMode == null ? 0 : transition_duration; if (mode >= DisplayMode.Full) - rankLabelStandalone.FadeIn(transition_duration, Easing.OutQuint).ResizeWidthTo(rank_label_width, transition_duration, Easing.OutQuint); + rankLabelStandalone.FadeIn(duration, Easing.OutQuint).ResizeWidthTo(rank_label_width, duration, Easing.OutQuint); else - rankLabelStandalone.FadeOut(transition_duration, Easing.OutQuint).ResizeWidthTo(0, transition_duration, Easing.OutQuint); + rankLabelStandalone.FadeOut(duration, Easing.OutQuint).ResizeWidthTo(0, duration, Easing.OutQuint); if (mode >= DisplayMode.Regular) { - statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); + statisticsContainer.FadeIn(duration, Easing.OutQuint).MoveToX(0, duration, Easing.OutQuint); statisticsContainer.Direction = FillDirection.Horizontal; - statisticsContainer.ScaleTo(1, transition_duration, Easing.OutQuint); + statisticsContainer.ScaleTo(1, duration, Easing.OutQuint); } else if (mode >= DisplayMode.Compact) { - statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); + statisticsContainer.FadeIn(duration, Easing.OutQuint).MoveToX(0, duration, Easing.OutQuint); statisticsContainer.Direction = FillDirection.Vertical; - statisticsContainer.ScaleTo(0.8f, transition_duration, Easing.OutQuint); + statisticsContainer.ScaleTo(0.8f, duration, Easing.OutQuint); } else - statisticsContainer.FadeOut(transition_duration, Easing.OutQuint).MoveToX(statisticsContainer.DrawWidth, transition_duration, Easing.OutQuint); + statisticsContainer.FadeOut(duration, Easing.OutQuint).MoveToX(statisticsContainer.DrawWidth, duration, Easing.OutQuint); currentMode = mode; } From 7c6a1f2502d55d8a6814fdbebd8f77d1f90c440b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Apr 2025 18:43:12 +0900 Subject: [PATCH 10/21] Update fetch logic to match existing leaderboard Also handles some display edge cases where scores may overlap placeholder or do other weird things. --- .../TestSceneBeatmapLeaderboardWedge.cs | 1 + .../SelectV2/BeatmapLeaderboardWedge.cs | 78 +++++++------------ 2 files changed, 28 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs index f03d83b5e8..61d23c4513 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardWedge.cs @@ -137,6 +137,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep("ensure no scores displayed", () => leaderboard.SetScores(Array.Empty())); + AddStep(@"Retrieving", () => leaderboard.SetState(LeaderboardState.Retrieving)); AddStep(@"Network failure", () => leaderboard.SetState(LeaderboardState.NetworkFailure)); AddStep(@"No team", () => leaderboard.SetState(LeaderboardState.NoTeam)); AddStep(@"No supporter", () => leaderboard.SetState(LeaderboardState.NotSupporter)); diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index 774c1540c7..c6e110b282 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -12,14 +12,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; -using osu.Game.Online.API; using osu.Game.Online.Leaderboards; using osu.Game.Online.Placeholders; using osu.Game.Overlays; @@ -54,21 +52,16 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private IBindable> mods { get; set; } = null!; - [Resolved] - private IAPIProvider api { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; public IBindable Scope { get; } = new Bindable(); - private bool isOnlineScope => Scope.Value != BeatmapLeaderboardScope.Local; - public IBindable FilterBySelectedMods { get; } = new BindableBool(); private CancellationTokenSource? cancellationTokenSource; - private readonly Bindable fetchedScores = new Bindable(); + private readonly IBindable fetchedScores = new Bindable(); [BackgroundDependencyLoader] private void load() @@ -147,7 +140,7 @@ namespace osu.Game.Screens.SelectV2 } }; - ((IBindable)fetchedScores).BindTo(leaderboardManager.Scores); + fetchedScores.BindTo(leaderboardManager.Scores); } protected override void LoadComplete() @@ -179,10 +172,11 @@ namespace osu.Game.Screens.SelectV2 refetchScores(); } + private bool initialFetchComplete; + private void refetchScores() { SetScores(Array.Empty(), null); - SetState(LeaderboardState.Retrieving); if (beatmap.IsDefault) { @@ -190,55 +184,35 @@ namespace osu.Game.Screens.SelectV2 return; } + SetState(LeaderboardState.Retrieving); + var fetchBeatmapInfo = beatmap.Value.BeatmapInfo; var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; - if (!api.IsLoggedIn && isOnlineScope) + // For now, we forcefully refresh to keep things simple. + // In the future, removing this requirement may be deemed useful, but will need ample testing of edge case scenarios + // (like returning from gameplay after setting a new score, returning to song select after main menu). + leaderboardManager.FetchWithCriteria(new LeaderboardCriteria(fetchBeatmapInfo, fetchRuleset, Scope.Value, FilterBySelectedMods.Value ? mods.Value.ToArray() : null), forceRefresh: true); + + if (!initialFetchComplete) { - SetState(LeaderboardState.NotLoggedIn); - return; + // only bind this after the first fetch to avoid reading stale scores. + fetchedScores.BindTo(leaderboardManager.Scores); + fetchedScores.BindValueChanged(_ => updateScores(), true); + initialFetchComplete = true; } + } - if (!fetchRuleset.IsLegacyRuleset()) - { - SetState(LeaderboardState.RulesetUnavailable); - return; - } + private void updateScores() + { + var scores = fetchedScores.Value; - if ((fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) && isOnlineScope) - { - SetState(LeaderboardState.BeatmapUnavailable); - return; - } + if (scores == null) return; - if (Scope.Value.RequiresSupporter(FilterBySelectedMods.Value) && !api.LocalUser.Value.IsSupporter) - { - SetState(LeaderboardState.NotSupporter); - return; - } - - if (Scope.Value == BeatmapLeaderboardScope.Team && api.LocalUser.Value.Team == null) - { - SetState(LeaderboardState.NoTeam); - return; - } - - leaderboardManager.FetchWithCriteriaAsync(new LeaderboardCriteria(fetchBeatmapInfo, fetchRuleset, Scope.Value, FilterBySelectedMods.Value ? mods.Value.ToArray() : null)) - .ContinueWith(t => - { - if (t.Exception != null && !t.IsCanceled) - { - Schedule(() => SetState(LeaderboardState.NetworkFailure)); - return; - } - - fetchedScores.UnbindEvents(); - fetchedScores.BindValueChanged(scores => - { - if (scores.NewValue != null) - Schedule(() => SetScores(scores.NewValue.TopScores, scores.NewValue.UserScore)); - }, true); - }); + if (scores.FailState != null) + SetState((LeaderboardState)scores.FailState); + else + SetScores(scores.TopScores, scores.UserScore); } protected void SetScores(IEnumerable scores, ScoreInfo? userScore) @@ -349,6 +323,8 @@ namespace osu.Game.Screens.SelectV2 if (placeholder == null) return; + clearScores(); + placeholderContainer.Child = placeholder; placeholder.ScaleTo(0.8f).Then().ScaleTo(1, 900, Easing.OutQuint); From 698f9bd669f4537832a1e6f193b47199d0a0efb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Apr 2025 18:58:49 +0900 Subject: [PATCH 11/21] Begin to fix eyesore code in `BeatmapLeaderboardScore` --- .../SelectV2/BeatmapLeaderboardScore.cs | 123 +++++++++--------- 1 file changed, 60 insertions(+), 63 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index d76f2b181f..4f6a9df34a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -122,11 +122,11 @@ namespace osu.Game.Screens.SelectV2 public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(colourProvider); - public ScoreInfo TooltipContent { get; } + private readonly ScoreInfo score; public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true) { - TooltipContent = score; + this.score = score; this.sheared = sheared; Shear = sheared ? OsuGame.SHEAR : Vector2.Zero; @@ -137,14 +137,14 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - var user = TooltipContent.User; + var user = score.User; 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(TooltipContent).Select(s => new ScoreComponentLabel(s, TooltipContent) + statisticsLabels = getStatistics(score).Select(s => new ScoreComponentLabel(s, score) { // ensure statistics container is the correct width when invalidating AlwaysPresent = true, @@ -237,18 +237,18 @@ namespace osu.Game.Screens.SelectV2 { int maxMods = scoringMode.Value == ScoringMode.Standardised ? 4 : 5; - if (TooltipContent.Mods.Length > 0) + if (score.Mods.Length > 0) { modsContainer.Padding = new MarginPadding { Top = 4f }; - modsContainer.ChildrenEnumerable = TooltipContent.Mods.AsOrdered().Take(Math.Min(maxMods, TooltipContent.Mods.Length)).Select(mod => new ColouredModSwitchTiny(mod) + modsContainer.ChildrenEnumerable = score.Mods.AsOrdered().Take(Math.Min(maxMods, score.Mods.Length)).Select(mod => new ColouredModSwitchTiny(mod) { Scale = new Vector2(0.3125f) }); - if (TooltipContent.Mods.Length > maxMods) + if (score.Mods.Length > maxMods) { modsContainer.Remove(modsContainer[^1], true); - modsContainer.Add(new MoreModSwitchTiny(TooltipContent.Mods) + modsContainer.Add(new MoreModSwitchTiny(score.Mods) { Scale = new Vector2(0.3125f), }); @@ -272,7 +272,7 @@ namespace osu.Game.Screens.SelectV2 new UserCoverBackground { RelativeSizeAxes = Axes.Both, - User = TooltipContent.User, + User = score.User, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -363,7 +363,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.CentreLeft, Size = new Vector2(30, 15), }, - new DateLabel(TooltipContent.Date) + new DateLabel(score.Date) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -425,7 +425,7 @@ namespace osu.Game.Screens.SelectV2 Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(TooltipContent.Rank)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank)), }, }, new Box @@ -434,7 +434,7 @@ namespace osu.Game.Screens.SelectV2 Width = grade_width, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Colour = OsuColour.ForRank(TooltipContent.Rank), + Colour = OsuColour.ForRank(score.Rank), }, new TrianglesV2 { @@ -443,7 +443,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.TopRight, SpawnRatio = 2, Velocity = 0.7f, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(TooltipContent.Rank).Darken(0.2f)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Darken(0.2f)), }, new Container { @@ -457,9 +457,9 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.Centre, Origin = Anchor.Centre, Spacing = new Vector2(-2), - Colour = DrawableRank.GetRankNameColour(TooltipContent.Rank), + Colour = DrawableRank.GetRankNameColour(score.Rank), Font = OsuFont.Numeric.With(size: 14), - Text = DrawableRank.GetRankName(TooltipContent.Rank), + Text = DrawableRank.GetRankName(score.Rank), ShadowColour = Color4.Black.Opacity(0.3f), ShadowOffset = new Vector2(0, 0.08f), Shadow = true, @@ -489,7 +489,7 @@ namespace osu.Game.Screens.SelectV2 new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(TooltipContent.Rank).Opacity(0.5f)), + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Opacity(0.5f)), }, new FillFlowContainer { @@ -506,7 +506,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.TopRight, UseFullGlyphHeight = false, Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Current = scoreManager.GetBindableTotalScoreString(TooltipContent), + Current = scoreManager.GetBindableTotalScoreString(score), Spacing = new Vector2(-1.5f), Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), }, @@ -652,7 +652,31 @@ namespace osu.Game.Screens.SelectV2 return DisplayMode.Minimal; } - #region Subclasses + ScoreInfo IHasCustomTooltip.TooltipContent => score; + + MenuItem[] IHasContextMenu.ContextMenuItems + { + get + { + List items = new List(); + + // system mods should never be copied across regardless of anything. + var copyableMods = score.Mods.Where(m => IsValidMod.Invoke(m) && m.Type != ModType.System).ToArray(); + + if (copyableMods.Length > 0) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = copyableMods)); + + if (score.OnlineID > 0) + items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{score.OnlineID}"))); + + if (score.Files.Count <= 0) return items.ToArray(); + + items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); + items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + + return items.ToArray(); + } + } private enum DisplayMode { @@ -753,19 +777,18 @@ namespace osu.Game.Screens.SelectV2 private sealed partial class ColouredModSwitchTiny : ModSwitchTiny, IHasCustomTooltip { - public Mod? TooltipContent { get; } - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; public ColouredModSwitchTiny(Mod mod) : base(mod) { - TooltipContent = mod; Active.Value = true; } public ITooltip GetCustomTooltip() => new ModTooltip(colourProvider); + + Mod? IHasCustomTooltip.TooltipContent => (Mod)Mod; } private sealed partial class MoreModSwitchTiny : CompositeDrawable, IHasPopover @@ -820,52 +843,26 @@ namespace osu.Game.Screens.SelectV2 protected override bool OnHover(HoverEvent e) => true; public Popover GetPopover() => new MoreModsPopover(mods); - } - public partial class MoreModsPopover : OsuPopover - { - public MoreModsPopover(IReadOnlyList mods) + public partial class MoreModsPopover : OsuPopover { - AutoSizeAxes = Axes.Both; - AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; - - Child = new FillFlowContainer + public MoreModsPopover(IReadOnlyList mods) { - Width = 125f, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - Spacing = new Vector2(2.5f), - ChildrenEnumerable = mods.AsOrdered().Select(m => new ColouredModSwitchTiny(m) + AutoSizeAxes = Axes.Both; + AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; + + Child = new FillFlowContainer { - Scale = new Vector2(0.3125f), - }) - }; - } - } - - #endregion - - public MenuItem[] ContextMenuItems - { - get - { - List items = new List(); - - // system mods should never be copied across regardless of anything. - var copyableMods = TooltipContent.Mods.Where(m => IsValidMod.Invoke(m) && m.Type != ModType.System).ToArray(); - - if (copyableMods.Length > 0) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = copyableMods)); - - if (TooltipContent.OnlineID > 0) - items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => clipboard?.SetText($@"{api.Endpoints.WebsiteUrl}/scores/{TooltipContent.OnlineID}"))); - - if (TooltipContent.Files.Count <= 0) return items.ToArray(); - - items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(TooltipContent))); - items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(TooltipContent)))); - - return items.ToArray(); + 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), + }) + }; + } } } } From d13c7e69955086485c14f4df1aa5cc40c51640c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Apr 2025 19:31:16 +0900 Subject: [PATCH 12/21] Remove all animations from `BeatmapLeaderboardScore` and fix more eyesore code --- .../SelectV2/BeatmapLeaderboardScore.cs | 668 ++++++++---------- 1 file changed, 305 insertions(+), 363 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index 4f6a9df34a..c573239623 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -27,7 +27,6 @@ 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; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; @@ -47,6 +46,8 @@ namespace osu.Game.Screens.SelectV2 { public sealed partial class BeatmapLeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { + public const int HEIGHT = 50; + public Bindable> SelectedMods = new Bindable>(); /// @@ -59,28 +60,6 @@ namespace osu.Game.Screens.SelectV2 public int? Rank { get; init; } public bool IsPersonalBest { get; init; } - 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 bool sheared; - - public const int HEIGHT = 50; - - private const int corner_radius = 10; - private const int transition_duration = 200; - - private Colour4 foregroundColour; - 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!; @@ -90,29 +69,45 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private ScoreManager scoreManager { get; set; } = null!; + [Resolved] + private OsuConfigManager config { get; set; } = null!; + [Resolved] private Clipboard? clipboard { get; set; } [Resolved] private IAPIProvider api { get; set; } = null!; - private Container content = null!; + 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 const int corner_radius = 10; + private const int transition_duration = 200; + + private static readonly Color4 personal_best_gradient_left = Color4Extensions.FromHex("#66FFCC"); + private static readonly Color4 personal_best_gradient_right = Color4Extensions.FromHex("#51A388"); + + private Colour4 foregroundColour; + private Colour4 backgroundColour; + private ColourInfo totalScoreBackgroundGradient; + + private ColourInfo personalBestGradient; + + private IBindable scoringMode { get; set; } = null!; + private Box background = null!; private Box foreground = null!; - private Drawable avatar = null!; private ClickableAvatar innerAvatar = null!; - private OsuSpriteText nameLabel = null!; - private List statisticsLabels = null!; - private Container rightContent = null!; - private FillFlowContainer flagBadgeAndDateContainer = null!; private FillFlowContainer modsContainer = null!; - private OsuSpriteText scoreText = null!; - private Drawable scoreRank = null!; private Box totalScoreBackground = null!; private FillFlowContainer statisticsContainer = null!; @@ -120,10 +115,10 @@ namespace osu.Game.Screens.SelectV2 private Container rankLabelStandalone = null!; private Container rankLabelOverlay = null!; - public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(colourProvider); - private readonly ScoreInfo score; + private readonly bool sheared; + public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true) { this.score = score; @@ -137,20 +132,12 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - var user = score.User; - 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) - { - // ensure statistics container is the correct width when invalidating - AlwaysPresent = true, - }).ToList(); - - Child = content = new Container + Child = new Container { Masking = true, CornerRadius = corner_radius, @@ -195,8 +182,279 @@ namespace osu.Game.Screens.SelectV2 } }, }, - createCentreContent(user), - createRightContent() + new Container + { + Name = @"Centre container", + Masking = true, + CornerRadius = corner_radius, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + foreground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = foregroundColour + }, + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + User = score.User, + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Colour = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0.5f), Colour4.FromHex(@"222A27").Opacity(1)), + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + CornerRadius = corner_radius, + Masking = true, + Children = new Drawable[] + { + new DelayedLoadWrapper(innerAvatar = new ClickableAvatar(score.User) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.1f), + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + RelativeSizeAxes = Axes.Both, + }) + { + RelativeSizeAxes = Axes.None, + Size = new Vector2(HEIGHT) + }, + rankLabelOverlay = new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Black.Opacity(0.5f), + }, + new RankLabel(Rank, sheared, false) + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + } + }, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = corner_radius }, + Children = new Drawable[] + { + new FillFlowContainer + { + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new UpdateableFlag(score.User.CountryCode) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20, 14), + }, + new UpdateableTeamFlag(score.User.Team) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30, 15), + }, + new DateLabel(score.Date) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = colourProvider.Content2, + UseFullGlyphHeight = false, + } + } + }, + new TruncatingSpriteText + { + RelativeSizeAxes = Axes.X, + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Text = score.User.Username, + Font = OsuFont.Style.Heading2, + } + } + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Child = statisticsContainer = new FillFlowContainer + { + Name = @"Statistics container", + Padding = new MarginPadding { Right = 40 }, + Spacing = new Vector2(25, 0), + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = getStatistics(score).Select(s => new ScoreComponentLabel(s, score)).ToList(), + Alpha = 0, + } + } + } + }, + }, + }, + }, + rightContent = new Container + { + Name = @"Right content", + RelativeSizeAxes = Axes.Y, + Child = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = grade_width }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank)), + }, + }, + new Box + { + RelativeSizeAxes = Axes.Y, + Width = grade_width, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = OsuColour.ForRank(score.Rank), + }, + new TrianglesV2 + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + SpawnRatio = 2, + Velocity = 0.7f, + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Darken(0.2f)), + }, + new Container + { + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = grade_width, + Child = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(-2), + Colour = DrawableRank.GetRankNameColour(score.Rank), + Font = OsuFont.Numeric.With(size: 14), + Text = DrawableRank.GetRankName(score.Rank), + ShadowColour = Color4.Black.Opacity(0.3f), + ShadowOffset = new Vector2(0, 0.08f), + Shadow = true, + UseFullGlyphHeight = false, + }, + }, + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding { Right = grade_width }, + Child = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Masking = true, + CornerRadius = corner_radius, + Children = new Drawable[] + { + totalScoreBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = totalScoreBackgroundGradient, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Opacity(0.5f)), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = corner_radius }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + UseFullGlyphHeight = false, + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + Current = scoreManager.GetBindableTotalScoreString(score), + Spacing = new Vector2(-1.5f), + Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), + }, + new InputBlockingContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Child = modsContainer = new FillFlowContainer + { + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f, 0f), + }, + }, + } + } + } + } + } + } + }, + } } } } @@ -206,11 +464,6 @@ namespace osu.Game.Screens.SelectV2 innerAvatar.OnLoadComplete += d => d.FadeInFromZero(200); } - [Resolved] - private OsuConfigManager config { get; set; } = null!; - - private IBindable scoringMode { get; set; } = null!; - protected override void LoadComplete() { base.LoadComplete(); @@ -256,325 +509,12 @@ namespace osu.Game.Screens.SelectV2 } } - private Container createCentreContent(APIUser user) => new Container - { - Name = @"Centre container", - Masking = true, - CornerRadius = corner_radius, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - foreground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = foregroundColour - }, - new UserCoverBackground - { - RelativeSizeAxes = Axes.Both, - User = score.User, - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Colour = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0.5f), Colour4.FromHex(@"222A27").Opacity(1)), - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - new Container - { - AutoSizeAxes = Axes.Both, - CornerRadius = corner_radius, - Masking = true, - Children = new[] - { - avatar = new DelayedLoadWrapper( - innerAvatar = new ClickableAvatar(user) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(1.1f), - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - RelativeSizeAxes = Axes.Both, - }) - { - RelativeSizeAxes = Axes.None, - Size = new Vector2(HEIGHT) - }, - rankLabelOverlay = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Black.Opacity(0.5f), - }, - new RankLabel(Rank, sheared, false) - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - } - } - }, - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = corner_radius }, - Children = new Drawable[] - { - flagBadgeAndDateContainer = new FillFlowContainer - { - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - AutoSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new UpdateableFlag(user.CountryCode) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20, 14), - }, - new UpdateableTeamFlag(user.Team) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 15), - }, - new DateLabel(score.Date) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = colourProvider.Content2, - UseFullGlyphHeight = false, - } - } - }, - nameLabel = new TruncatingSpriteText - { - RelativeSizeAxes = Axes.X, - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Text = user.Username, - Font = OsuFont.Style.Heading2, - } - } - }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Child = statisticsContainer = new FillFlowContainer - { - Name = @"Statistics container", - Padding = new MarginPadding { Right = 40 }, - Spacing = new Vector2(25, 0), - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = statisticsLabels, - Alpha = 0, - } - } - } - }, - }, - }, - }; - - private Container createRightContent() => rightContent = new Container - { - Name = @"Right content", - RelativeSizeAxes = Axes.Y, - Child = new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = grade_width }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank)), - }, - }, - new Box - { - RelativeSizeAxes = Axes.Y, - Width = grade_width, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Colour = OsuColour.ForRank(score.Rank), - }, - new TrianglesV2 - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - SpawnRatio = 2, - Velocity = 0.7f, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Darken(0.2f)), - }, - new Container - { - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Y, - Width = grade_width, - Child = scoreRank = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(-2), - Colour = DrawableRank.GetRankNameColour(score.Rank), - Font = OsuFont.Numeric.With(size: 14), - Text = DrawableRank.GetRankName(score.Rank), - ShadowColour = Color4.Black.Opacity(0.3f), - ShadowOffset = new Vector2(0, 0.08f), - Shadow = true, - UseFullGlyphHeight = false, - }, - }, - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Right = grade_width }, - Child = new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Masking = true, - CornerRadius = corner_radius, - Children = new Drawable[] - { - totalScoreBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = totalScoreBackgroundGradient, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), OsuColour.ForRank(score.Rank).Opacity(0.5f)), - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = corner_radius }, - Children = new Drawable[] - { - scoreText = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - UseFullGlyphHeight = false, - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - Current = scoreManager.GetBindableTotalScoreString(score), - Spacing = new Vector2(-1.5f), - Font = OsuFont.Style.Subtitle.With(weight: FontWeight.Light, fixedWidth: true), - }, - new InputBlockingContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Child = modsContainer = new FillFlowContainer - { - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0f), - }, - }, - } - } - } - } - } - } - }, - }; - private (CaseTransformableString, LocalisableString DisplayAccuracy)[] getStatistics(ScoreInfo model) => new[] { (BeatmapsetsStrings.ShowScoreboardHeadersCombo.ToUpper(), model.MaxCombo.ToString().Insert(model.MaxCombo.ToString().Length, "x")), (BeatmapsetsStrings.ShowScoreboardHeadersAccuracy.ToUpper(), model.DisplayAccuracy), }; - public override void Show() - { - foreach (var d in new[] { avatar, nameLabel, scoreText, scoreRank, flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels)) - d.FadeOut(); - - Alpha = 0; - - content.MoveToY(60); - avatar.MoveToX(60); - nameLabel.MoveToX(125); - - this.FadeIn(200); - content.MoveToY(0, 800, Easing.OutQuint); - - using (BeginDelayedSequence(100)) - { - avatar.FadeIn(300, Easing.OutQuint); - nameLabel.FadeIn(350, Easing.OutQuint); - - avatar.MoveToX(0, 300, Easing.OutQuint); - nameLabel.MoveToX(0, 350, Easing.OutQuint); - - using (BeginDelayedSequence(250)) - { - scoreText.FadeIn(200); - scoreRank.FadeIn(200); - - using (BeginDelayedSequence(50)) - { - var drawables = new Drawable[] { flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels).ToArray(); - for (int i = 0; i < drawables.Length; i++) - drawables[i].FadeIn(100 + i * 50); - } - } - } - } - protected override bool OnHover(HoverEvent e) { updateState(); @@ -652,6 +592,8 @@ namespace osu.Game.Screens.SelectV2 return DisplayMode.Minimal; } + ITooltip IHasCustomTooltip.GetCustomTooltip() => new LeaderboardScoreTooltip(colourProvider); + ScoreInfo IHasCustomTooltip.TooltipContent => score; MenuItem[] IHasContextMenu.ContextMenuItems @@ -788,7 +730,7 @@ namespace osu.Game.Screens.SelectV2 public ITooltip GetCustomTooltip() => new ModTooltip(colourProvider); - Mod? IHasCustomTooltip.TooltipContent => (Mod)Mod; + Mod IHasCustomTooltip.TooltipContent => (Mod)Mod; } private sealed partial class MoreModSwitchTiny : CompositeDrawable, IHasPopover From 3b2e8281b4e3f2ecdf5d6821427973d9004bb01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 23 Apr 2025 13:22:25 +0200 Subject: [PATCH 13/21] Remove double binding --- osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index c6e110b282..e4df89c1f5 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -139,8 +139,6 @@ namespace osu.Game.Screens.SelectV2 loading = new LoadingLayer(), } }; - - fetchedScores.BindTo(leaderboardManager.Scores); } protected override void LoadComplete() From bd58aac9cca8b410b550ce6fa8afcdfb68a14b5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Apr 2025 20:59:07 +0900 Subject: [PATCH 14/21] Begin to fix `BeatmapLeaderboardWedge` code quality --- .../SelectV2/BeatmapLeaderboardWedge.cs | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index e4df89c1f5..b8c4d07d04 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -25,20 +25,15 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; +using osuTK; namespace osu.Game.Screens.SelectV2 { public partial class BeatmapLeaderboardWedge : VisibilityContainer { - private Container scoresContainer = null!; + public IBindable Scope { get; } = new Bindable(); - private OsuScrollContainer scoresScroll = null!; - private Container personalBestDisplay = null!; - private Container personalBestScoreContainer = null!; - private LoadingLayer loading = null!; - - private Container placeholderContainer = null!; - private Placeholder? placeholder; + public IBindable FilterBySelectedMods { get; } = new BindableBool(); [Resolved] private LeaderboardManager leaderboardManager { get; set; } = null!; @@ -55,14 +50,23 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public IBindable Scope { get; } = new Bindable(); + private Container placeholderContainer = null!; + private Placeholder? placeholder; - public IBindable FilterBySelectedMods { get; } = new BindableBool(); + private Container scoresContainer = null!; + + private OsuScrollContainer scoresScroll = null!; + private Container personalBestDisplay = null!; + + private Container personalBestScoreContainer = null!; + private LoadingLayer loading = null!; private CancellationTokenSource? cancellationTokenSource; private readonly IBindable fetchedScores = new Bindable(); + private const float personal_best_height = 80; + [BackgroundDependencyLoader] private void load() { @@ -82,7 +86,15 @@ namespace osu.Game.Screens.SelectV2 { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 4f, Bottom = 180f }, + Padding = new MarginPadding + { + Top = 5, + // Left padding offsets the shear to create a visually appealing list display. + Left = 80f, + // Bottom padding ensures the last entry's full width is displayed + // (ie it is fully on screen after shear is considered). + Bottom = BeatmapLeaderboardScore.HEIGHT * 3 + }, }, }, personalBestDisplay = new Container @@ -90,9 +102,9 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + Height = personal_best_height, Shear = OsuGame.SHEAR, - Margin = new MarginPadding { Left = -60f }, + Margin = new MarginPadding { Left = -40f }, CornerRadius = 10f, Masking = true, // push the personal best 1px down to hide masking issues @@ -111,7 +123,7 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Shear = -OsuGame.SHEAR, - Padding = new MarginPadding { Top = 5f, Bottom = 30f, Left = 100f, Right = 30f }, + Padding = new MarginPadding { Top = 5f, Bottom = 5f, Left = 70f, Right = 10f }, Children = new Drawable[] { new OsuSpriteText @@ -239,24 +251,19 @@ namespace osu.Game.Screens.SelectV2 foreach (var d in loadedScores) { - Container animContainer; + d.Y = (BeatmapLeaderboardScore.HEIGHT + 4f) * i; - scoresContainer.Add(animContainer = new Container - { - Shear = -OsuGame.SHEAR, - Y = (BeatmapLeaderboardScore.HEIGHT + 4f) * i, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0f, - Padding = new MarginPadding { Left = 80f }, - Child = d, - }); + // This is a bit of a weird one. We're already in a sheared state and don't want top-level + // shear applied, but still need the `BeatmapLeadeboardScore` to be in "sheared" mode (see ctor). + d.Shear = Vector2.Zero; - animContainer - .MoveToX(-20f) - .Delay(delay) - .FadeIn(300, Easing.OutQuint) - .MoveToX(0f, 300, Easing.OutQuint); + scoresContainer.Add(d); + + d.FadeOut() + .MoveToX(-20f) + .Delay(delay) + .FadeIn(300, Easing.OutQuint) + .MoveToX(0f, 300, Easing.OutQuint); delay += 30; i++; @@ -274,7 +281,7 @@ namespace osu.Game.Screens.SelectV2 SelectedMods = { BindTarget = mods }, }; - scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding { Bottom = 100 }, 300, Easing.OutQuint); + scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding { Bottom = personal_best_height }, 300, Easing.OutQuint); } } From 6cdbfe064799b46818ce49be3926e6f70d9191c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Apr 2025 21:22:01 +0900 Subject: [PATCH 15/21] Update 404ing cover image URLs --- osu.Game.Tests/Resources/TestResources.cs | 7 +++++- .../TestSceneDailyChallengeCarousel.cs | 5 ++-- .../TestSceneDailyChallengeEventFeed.cs | 8 +++---- .../TestSceneDailyChallengeScoreBreakdown.cs | 5 ++-- .../TestSceneDailyChallengeTotalsDisplay.cs | 4 ++-- .../TestSceneMultiplayerParticipantsList.cs | 23 ++++++++++--------- .../Online/TestSceneDashboardOverlay.cs | 3 ++- .../Visual/Online/TestSceneFriendDisplay.cs | 5 ++-- .../Online/TestSceneUserClickableAvatar.cs | 7 +++--- .../Online/TestSceneUserProfileHeader.cs | 5 ++-- .../Online/TestSceneUserProfileOverlay.cs | 11 +++++---- .../TestScenePlaylistsResultsScreen.cs | 6 ++--- .../TestSceneBeatmapLeaderboardScore.cs | 6 ++--- 13 files changed, 54 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index e0572e604c..54204d412a 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -29,6 +29,11 @@ namespace osu.Game.Tests.Resources { public const double QUICK_BEATMAP_LENGTH = 10000; + public const string COVER_IMAGE_1 = "https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg"; + public const string COVER_IMAGE_2 = "https://assets.ppy.sh/user-cover-presets/7/4a0ccb7b7fdd5c4238b11f0e7c686760fe2c99c6472b19400e82d1a8ff503e31.jpeg"; + public const string COVER_IMAGE_3 = "https://assets.ppy.sh/user-cover-presets/12/6e8d3402c8080c2d9549a98321e1bff111dd9c94603ccdb237597479cab6e8a7.jpeg"; + public const string COVER_IMAGE_4 = "https://assets.ppy.sh/user-cover-presets/17/80f82e4c2b27d8d6eed3ce89708ec27343e5ac63389cba6b5fb4550776562d08.jpeg"; + private static readonly TemporaryNativeStorage temp_storage = new TemporaryNativeStorage("TestResources"); public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly); @@ -178,7 +183,7 @@ namespace osu.Game.Tests.Resources { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = COVER_IMAGE_3, }, BeatmapInfo = beatmap, BeatmapHash = beatmap.Hash, diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs index b9470f3be4..becce7b22a 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs @@ -16,6 +16,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.DailyChallenge { @@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), null); feed.AddNewScore(ev); @@ -141,7 +142,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), RNG.Next(1, 1000)); feed.AddNewScore(ev); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs index 4b784f661d..eda596effb 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), null); feed.AddNewScore(ev); @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), RNG.Next(11, 1000)); var testScore = TestResources.CreateTestScoreInfo(); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), RNG.Next(1, 10)); feed.AddNewScore(ev); @@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), null); feed.AddNewScore(ev); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index b04696aded..b4e1ffffdb 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -13,6 +13,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.DailyChallenge { @@ -65,7 +66,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), null); breakdown.AddNewScore(ev); @@ -85,7 +86,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), null); breakdown.AddNewScore(ev); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs index ae212f5212..4619fad938 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), null); totals.AddNewScore(ev); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge { Id = 2, Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, RNG.Next(1_000_000), RNG.Next(11, 1000)); var testScore = TestResources.CreateTestScoreInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index ed3fd4a6f8..158a1f46a0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; +using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -46,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 3, Username = "Second", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, })); AddAssert("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); @@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 3, Username = "Second", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }); }); @@ -159,7 +160,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 3, Username = "Second", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, })); AddUntilStep("first user crown visible", () => this.ChildrenOfType().ElementAt(0).ChildrenOfType().First().Alpha == 1); @@ -178,7 +179,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 3, Username = "Second", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, })); AddStep("make second user host", () => MultiplayerClient.TransferHost(3)); @@ -197,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 3, Username = "Second", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, })); AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1); @@ -218,7 +219,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 3, Username = "Second", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, })); AddStep("kick second user", () => this.ChildrenOfType().Single(d => d.IsPresent).TriggerClick()); @@ -246,7 +247,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }); MultiplayerClient.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1)); @@ -293,7 +294,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }); MultiplayerClient.ChangeUserMods(0, new Mod[] @@ -330,7 +331,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }); MultiplayerClient.ChangeUserStyle(0, 259, 2); @@ -366,7 +367,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }); MultiplayerClient.ChangeUserMods(0, new Mod[] { @@ -415,7 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }); MultiplayerClient.ChangeUserBeatmapAvailability(0, BeatmapAvailability.LocallyAvailable()); diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs index fb54e936bc..13b7e6e18c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Online { @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"peppy", Id = 2, Colour = "99EB47", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, IsSupporter = supportLevel > 0, SupportLevel = supportLevel } diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 52905fe5da..805ac44829 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Metadata; using osu.Game.Overlays; using osu.Game.Overlays.Dashboard.Friends; +using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Metadata; using osu.Game.Users; @@ -237,7 +238,7 @@ namespace osu.Game.Tests.Visual.Online WasRecentlyOnline = true, Statistics = new UserStatistics { GlobalRank = 1111 }, CountryCode = CountryCode.JP, - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + CoverUrl = TestResources.COVER_IMAGE_4 }, new APIUser { @@ -246,7 +247,7 @@ namespace osu.Game.Tests.Visual.Online WasRecentlyOnline = false, Statistics = new UserStatistics { GlobalRank = 2222 }, CountryCode = CountryCode.AU, - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, IsSupporter = true, SupportLevel = 3, }, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs index 29272f7336..3333eae567 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserClickableAvatar.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Testing; using osu.Game.Graphics.Cursor; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Tests.Resources; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -30,9 +31,9 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(10f), Children = new[] { - generateUser(@"peppy", 2, CountryCode.AU, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false, "99EB47"), - generateUser(@"flyte", 3103765, CountryCode.JP, @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", true), - generateUser(@"joshika39", 17032217, CountryCode.RS, @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", false), + generateUser(@"peppy", 2, CountryCode.AU, TestResources.COVER_IMAGE_3, false, "99EB47"), + generateUser(@"flyte", 3103765, CountryCode.JP, TestResources.COVER_IMAGE_4, true), + generateUser(@"joshika39", 17032217, CountryCode.RS, TestResources.COVER_IMAGE_3, false), new UpdateableAvatar(), new UpdateableAvatar() }, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 193b356d71..d3be8d3b98 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -18,6 +18,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -136,7 +137,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 727, Username = "SomeoneIndecisive", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + CoverUrl = TestResources.COVER_IMAGE_1, Groups = new[] { new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" }, @@ -162,7 +163,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 728, Username = "Certain Guy", - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + CoverUrl = TestResources.COVER_IMAGE_2, Statistics = new UserStatistics { IsRanked = false, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 2972f69cba..1c2fdc7860 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Rulesets.Taiko; +using osu.Game.Tests.Resources; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -152,7 +153,7 @@ namespace osu.Game.Tests.Visual.Online Username = $"Colorful #{hue}", Id = 1, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + CoverUrl = TestResources.COVER_IMAGE_2, ProfileHue = hue, PlayMode = "osu", }); @@ -196,7 +197,7 @@ namespace osu.Game.Tests.Visual.Online Username = $"Colorful #{hue}", Id = 1, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + CoverUrl = TestResources.COVER_IMAGE_2, ProfileHue = hue, PlayMode = "osu", })); @@ -212,7 +213,7 @@ namespace osu.Game.Tests.Visual.Online Username = $"Colorful #{hue2}", Id = 2, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + CoverUrl = TestResources.COVER_IMAGE_2, ProfileHue = hue2, PlayMode = "osu", })); @@ -225,7 +226,7 @@ namespace osu.Game.Tests.Visual.Online Username = $"Colorful #{hue2}", Id = 2, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + CoverUrl = TestResources.COVER_IMAGE_2, ProfileHue = hue2, PlayMode = "osu", })); @@ -236,7 +237,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"Somebody", Id = 1, CountryCode = CountryCode.JP, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + CoverUrl = TestResources.COVER_IMAGE_1, JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, Groups = new[] diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 6b73f1a5f4..61269a7bf4 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -416,7 +416,7 @@ namespace osu.Game.Tests.Visual.Playlists { Id = 2 + i, Username = $"peppy{i}", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, }); @@ -432,7 +432,7 @@ namespace osu.Game.Tests.Visual.Playlists { Id = 2 + i, Username = $"peppy{i}", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, }); @@ -497,7 +497,7 @@ namespace osu.Game.Tests.Visual.Playlists { Id = 2 + i, Username = $"peppy{i}", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + CoverUrl = TestResources.COVER_IMAGE_3, }, }); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs index c2f1eb6b15..59bc17d75b 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Id = 6602580, Username = @"waaiiru", CountryCode = CountryCode.ES, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + CoverUrl = TestResources.COVER_IMAGE_1, }, Date = DateTimeOffset.Now.AddYears(-2), }; @@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Id = 6602580, Username = @"waaiiru", CountryCode = CountryCode.ES, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + CoverUrl = TestResources.COVER_IMAGE_1, }, Date = DateTimeOffset.Now.AddYears(-2), }, @@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Id = 1541390, Username = @"Toukai", CountryCode = CountryCode.CA, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + CoverUrl = TestResources.COVER_IMAGE_2, }, Date = DateTimeOffset.Now.AddMonths(-6), }, From 18060d30afbad9725e7cafe5c783c541cec6364d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Apr 2025 21:35:54 +0900 Subject: [PATCH 16/21] Fix user covers not loading if one corner is off-screen --- osu.Game/Users/UserCoverBackground.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index de6a306b2a..4d248d450b 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -33,7 +33,10 @@ namespace osu.Game.Users protected virtual double UnloadDelay => 5000; protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay); + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay) + { + RelativeSizeAxes = Axes.Both, + }; [LongRunningLoad] private partial class Cover : CompositeDrawable From d907719aa8ad676b14e17134ba86f95651ece89f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Apr 2025 16:14:12 +0900 Subject: [PATCH 17/21] Add tests with mods with adjusted settings --- .../Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs index 59bc17d75b..90a9310aeb 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs @@ -276,9 +276,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 scores[2].TotalScore = RNG.Next(120_000, 400_000); scores[2].MaximumStatistics[HitResult.Great] = 3000; - scores[1].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight() }; + scores[1].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 2 } }, new OsuModHardRock(), new OsuModFlashlight() }; scores[2].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight(), new OsuModClassic() }; - scores[3].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight(), new OsuModClassic(), new OsuModDifficultyAdjust() }; + scores[3].Mods = new Mod[] + { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight { ComboBasedSize = { Value = false } }, new OsuModClassic(), new OsuModDifficultyAdjust() }; scores[4].Mods = new ManiaRuleset().CreateAllMods().ToArray(); return scores; From 71620bfe267273ac37bb39eed5ca6629341cfc32 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 29 Apr 2025 06:03:28 +0300 Subject: [PATCH 18/21] Bring back full mod icons --- .../SelectV2/BeatmapLeaderboardScore.cs | 121 ++---------------- .../BeatmapLeaderboardScore_Tooltip.cs | 7 +- 2 files changed, 13 insertions(+), 115 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index c573239623..699a5216eb 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -6,7 +6,6 @@ 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; @@ -25,7 +24,6 @@ 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.Leaderboards; using osu.Game.Overlays; @@ -106,7 +104,7 @@ namespace osu.Game.Screens.SelectV2 private Container rightContent = null!; - private FillFlowContainer modsContainer = null!; + private FillFlowContainer modsContainer = null!; private Box totalScoreBackground = null!; @@ -422,6 +420,7 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.CentreLeft, Direction = FillDirection.Vertical, Padding = new MarginPadding { Horizontal = corner_radius }, + Spacing = new Vector2(0f, -2f), Children = new Drawable[] { new OsuSpriteText @@ -429,22 +428,22 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.TopRight, Origin = Anchor.TopRight, UseFullGlyphHeight = false, - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, 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, }, new InputBlockingContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Child = modsContainer = new FillFlowContainer + Child = modsContainer = new FillFlowContainer { - Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f, 0f), + Spacing = new Vector2(-10, 0), + Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, }, }, } @@ -488,24 +487,15 @@ namespace osu.Game.Screens.SelectV2 private void updateModDisplay() { - int maxMods = scoringMode.Value == ScoringMode.Standardised ? 4 : 5; - if (score.Mods.Length > 0) { modsContainer.Padding = new MarginPadding { Top = 4f }; - modsContainer.ChildrenEnumerable = score.Mods.AsOrdered().Take(Math.Min(maxMods, score.Mods.Length)).Select(mod => new ColouredModSwitchTiny(mod) + modsContainer.ChildrenEnumerable = score.Mods.AsOrdered().Select(mod => new ModIcon(mod) { - Scale = new Vector2(0.3125f) + Scale = new Vector2(0.3f), + // trim mod icon height down to its true height for alignment purposes. + Height = ModIcon.MOD_ICON_SIZE.Y * 3 / 4f, }); - - if (score.Mods.Length > maxMods) - { - modsContainer.Remove(modsContainer[^1], true); - modsContainer.Add(new MoreModSwitchTiny(score.Mods) - { - Scale = new Vector2(0.3125f), - }); - } } } @@ -716,96 +706,5 @@ namespace osu.Game.Screens.SelectV2 public LocalisableString TooltipText { get; } } - - private sealed partial class ColouredModSwitchTiny : ModSwitchTiny, IHasCustomTooltip - { - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - public ColouredModSwitchTiny(Mod mod) - : base(mod) - { - Active.Value = true; - } - - public ITooltip GetCustomTooltip() => new ModTooltip(colourProvider); - - Mod IHasCustomTooltip.TooltipContent => (Mod)Mod; - } - - private sealed partial class MoreModSwitchTiny : CompositeDrawable, IHasPopover - { - private readonly IReadOnlyList mods; - - public MoreModSwitchTiny(IReadOnlyList mods) - { - this.mods = mods; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Size = new Vector2(ModSwitchTiny.WIDTH, ModSwitchTiny.DEFAULT_HEIGHT); - - InternalChild = new CircularContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background6, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Bold), - Text = ". . .", - Colour = Color4.White, - UseFullGlyphHeight = false, - Margin = new MarginPadding - { - Top = 4 - } - } - } - }; - } - - 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), - }) - }; - } - } - } } } diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs index 7f1997522e..5813864a82 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs @@ -257,12 +257,11 @@ namespace osu.Game.Screens.SelectV2 { Show(); - modsFlow.ChildrenEnumerable = mods.AsOrdered().Select(m => new ModSwitchTiny(m) + modsFlow.ChildrenEnumerable = mods.AsOrdered().Select(m => new ModIcon(m) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scale = new Vector2(0.3125f), - Active = { Value = true }, + Scale = new Vector2(0.3f), }); } } @@ -301,7 +300,7 @@ namespace osu.Game.Screens.SelectV2 AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Bottom = 6f, Top = 6f + spacing }, Padding = new MarginPadding { Horizontal = 16f }, - Spacing = new Vector2(2.5f), + Spacing = new Vector2(2f, -4f), }, }; } From ca11f3348d53df325cde99c29ae6c1d8dc009a5c Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 29 Apr 2025 06:03:59 +0300 Subject: [PATCH 19/21] Add DA mod with custom adjustment in new score test scene --- .../Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs index 90a9310aeb..1b6d56df16 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapLeaderboardScore.cs @@ -279,7 +279,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 scores[1].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 2 } }, new OsuModHardRock(), new OsuModFlashlight() }; scores[2].Mods = new Mod[] { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight(), new OsuModClassic() }; scores[3].Mods = new Mod[] - { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight { ComboBasedSize = { Value = false } }, new OsuModClassic(), new OsuModDifficultyAdjust() }; + { new OsuModHidden(), new OsuModDoubleTime(), new OsuModHardRock(), new OsuModFlashlight { ComboBasedSize = { Value = false } }, new OsuModClassic(), new OsuModDifficultyAdjust { CircleSize = { Value = 3.2f } } }; scores[4].Mods = new ManiaRuleset().CreateAllMods().ToArray(); return scores; From fc0a233ba42b10f099434fd037f20dd45012d7e0 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 30 Apr 2025 07:20:15 +0300 Subject: [PATCH 20/21] Adjust right-side content layout to mask mods --- .../Screens/SelectV2/BeatmapLeaderboardScore.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index 699a5216eb..b422a6474e 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -334,8 +334,7 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.Y, Child = new Container { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Children = new Drawable[] @@ -390,15 +389,13 @@ namespace osu.Game.Screens.SelectV2 }, new Container { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Padding = new MarginPadding { Right = grade_width }, Child = new Container { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Masking = true, CornerRadius = corner_radius, Children = new Drawable[] @@ -416,8 +413,8 @@ namespace osu.Game.Screens.SelectV2 new FillFlowContainer { AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Direction = FillDirection.Vertical, Padding = new MarginPadding { Horizontal = corner_radius }, Spacing = new Vector2(0f, -2f), From cb3f8d7d835fa132b9603797a929f5197f8d6162 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Apr 2025 14:36:41 +0900 Subject: [PATCH 21/21] Remove colour lightening of judgement colours I'm not sure why this is a thing but let's not do it without proper rationale. --- .../Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs index 5813864a82..c6fe1e5f25 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore_Tooltip.cs @@ -102,13 +102,7 @@ namespace osu.Game.Screens.SelectV2 relativeDate.Date = value.Date; var judgementsStatistics = value.GetStatisticsForDisplay().Select(s => - { - Colour4 colour = colours.ForHitResult(s.Result); - var hsl = colour.ToHSL(); - - Colour4 lightColour = Colour4.FromHSL(hsl.X, hsl.Y, 0.8f); - return new StatisticRow(s.DisplayName.ToUpper(), lightColour, s.Count.ToLocalisableString("N0")); - }); + new StatisticRow(s.DisplayName.ToUpper(), colours.ForHitResult(s.Result), s.Count.ToLocalisableString("N0"))); double multiplier = 1.0;