From 3e240cbadec001ea77de2272ccbffe895491b898 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 17:11:40 +0900 Subject: [PATCH 01/53] Refactor sorting filter to better handle cases of beatmap set grouping Previously the logic would fall over under various scenarios due to applying aggregate logic where it shouldn't be, or vice-versa. Now the sorting filter explicitly checks the grouping mode and reacts accordingly. I was hoping we could avoid the sorting filter having any knowledge of grouping, but I don't see a way around this. --- .../SelectV2/BeatmapCarouselFilterSorting.cs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterSorting.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterSorting.cs index eb39b499de..0ebfc084bd 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterSorting.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterSorting.cs @@ -27,19 +27,27 @@ namespace osu.Game.Screens.SelectV2 { var criteria = getCriteria(); + bool groupedSets = BeatmapCarouselFilterGrouping.ShouldGroupBeatmapsTogether(criteria); + return items.Order(Comparer.Create((a, b) => { var ab = (BeatmapInfo)a.Model; var bb = (BeatmapInfo)b.Model; - if (ab.BeatmapSet!.Equals(bb.BeatmapSet)) - return compareDifficulty(ab, bb); + if (groupedSets) + { + if (ab.BeatmapSet!.Equals(bb.BeatmapSet)) + return compareDifficulty(ab, bb, criteria.Sort); - return compare(ab, bb, criteria.Sort); + // If we're grouping by sets, all fallback sorts need to be aggregates for the set. + return compare(ab, bb, criteria.Sort, aggregate: true); + } + + return compare(ab, bb, criteria.Sort, aggregate: false); })).ToList(); }, cancellationToken).ConfigureAwait(false); - private static int compare(BeatmapInfo a, BeatmapInfo b, SortMode sort) + private static int compare(BeatmapInfo a, BeatmapInfo b, SortMode sort, bool aggregate) { int comparison; @@ -80,15 +88,24 @@ namespace osu.Game.Screens.SelectV2 break; case SortMode.LastPlayed: - comparison = -compareUsingAggregateMax(a, b, static b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); + if (aggregate) + comparison = compareUsingAggregateMax(b, a, static b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); + else + comparison = Nullable.Compare(b.LastPlayed, a.LastPlayed); break; case SortMode.BPM: - comparison = compareUsingAggregateMax(a, b, static b => b.BPM); + if (aggregate) + comparison = compareUsingAggregateMax(a, b, static b => b.BPM); + else + comparison = a.BPM.CompareTo(b.BPM); break; case SortMode.Length: - comparison = compareUsingAggregateMax(a, b, static b => b.Length); + if (aggregate) + comparison = compareUsingAggregateMax(a, b, static b => b.Length); + else + comparison = a.Length.CompareTo(b.Length); break; default: @@ -108,7 +125,7 @@ namespace osu.Game.Screens.SelectV2 return comparison; } - private static int compareDifficulty(BeatmapInfo a, BeatmapInfo b) + private static int compareDifficulty(BeatmapInfo a, BeatmapInfo b, SortMode sort) { int comparison = a.Ruleset.CompareTo(b.Ruleset); From be688fd35090ec7fc362404dceb5105a15b381ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 19:21:53 +0900 Subject: [PATCH 02/53] Refactor grouping filter to also handle aggregate cases correctly This also fixes *another* inefficient case of full items iteration for no reason. --- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 86256ad99e..27f3dcf8ff 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -152,12 +152,15 @@ namespace osu.Game.Screens.SelectV2 case GroupMode.LastPlayed: return getGroupsBy(b => { - DateTimeOffset? maxLastPlayed = aggregateMax(b, items, bb => bb.LastPlayed); + var date = b.LastPlayed; - if (maxLastPlayed == null) + if (BeatmapSetsGroupedTogether) + date = aggregateMax(b, static b => (b.LastPlayed ?? DateTimeOffset.MinValue)); + + if (date == null) return new GroupDefinition(int.MaxValue, "Never"); - return defineGroupByDate(maxLastPlayed.Value); + return defineGroupByDate(date.Value); }, items); case GroupMode.RankedStatus: @@ -166,8 +169,12 @@ namespace osu.Game.Screens.SelectV2 case GroupMode.BPM: return getGroupsBy(b => { - double maxBPM = aggregateMax(b, items, bb => bb.BPM); - return defineGroupByBPM(maxBPM); + double bpm = b.BPM; + + if (BeatmapSetsGroupedTogether) + bpm = aggregateMax(b, bb => bb.BPM); + + return defineGroupByBPM(bpm); }, items); case GroupMode.Difficulty: @@ -176,8 +183,12 @@ namespace osu.Game.Screens.SelectV2 case GroupMode.Length: return getGroupsBy(b => { - double maxLength = aggregateMax(b, items, bb => bb.Length); - return defineGroupByLength(maxLength); + double length = b.Length; + + if (BeatmapSetsGroupedTogether) + length = aggregateMax(b, bb => bb.Length); + + return defineGroupByLength(length); }, items); case GroupMode.Collections: @@ -334,10 +345,10 @@ namespace osu.Game.Screens.SelectV2 return new GroupDefinition(11, "Over 10 minutes"); } - private static T? aggregateMax(BeatmapInfo b, IEnumerable items, Func func) + private static T? aggregateMax(BeatmapInfo b, Func func) { - var matchedBeatmaps = items.Select(i => i.Model).Cast().Where(beatmap => beatmap.BeatmapSet!.Equals(b.BeatmapSet)); - return matchedBeatmaps.Max(func); + var beatmaps = b.BeatmapSet!.Beatmaps.Where(bb => !bb.Hidden); + return beatmaps.Max(func); } private record GroupMapping(GroupDefinition? Group, List ItemsInGroup); From a8eff6b0964f92e7fdabba70ed5919fe5b7afbd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 17:13:00 +0900 Subject: [PATCH 03/53] Split out difficulties when sorting and grouping by "last played" --- .../Screens/SelectV2/BeatmapCarouselFilterGrouping.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 27f3dcf8ff..1174e172e1 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -124,7 +124,15 @@ namespace osu.Game.Screens.SelectV2 public static bool ShouldGroupBeatmapsTogether(FilterCriteria criteria) { - return criteria.Sort != SortMode.Difficulty && criteria.Group != GroupMode.Difficulty; + // In certain cases, we intentionally split out difficulties + // where it's more relevant or convenient to view them as individual items. + if (criteria.Sort == SortMode.Difficulty || criteria.Group == GroupMode.Difficulty) + return false; + if (criteria.Sort == SortMode.LastPlayed && criteria.Group == GroupMode.LastPlayed) + return false; + + // In the majority case we group sets together for display. + return true; } private List getGroups(List items, FilterCriteria criteria) From 3da4651ea2ee6096ba197d4827f346ced7ad5ce5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 May 2025 19:56:17 +0900 Subject: [PATCH 04/53] Reduce maximum sizing of left/right component areas --- osu.Game/Screens/SelectV2/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 504a55a4f8..ee82146a16 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -146,9 +146,9 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { - new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 850), + new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 660), new Dimension(), - new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 750), + new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 580), }, Content = new[] { From 88062d0a30a130fdf74c7c4def95ef838e492108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 May 2025 19:56:40 +0900 Subject: [PATCH 05/53] Fix leaderboard not showing last portion near bottom footer --- osu.Game/Screens/SelectV2/SongSelect.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index ee82146a16..84be157619 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -162,6 +162,11 @@ namespace osu.Game.Screens.SelectV2 // screen-wide scroll handling. Depth = float.MinValue, Shear = OsuGame.SHEAR, + Padding = new MarginPadding + { + Top = -CORNER_RADIUS_HIDE_OFFSET, + Left = -CORNER_RADIUS_HIDE_OFFSET, + }, Children = new Drawable[] { new Container @@ -177,11 +182,6 @@ namespace osu.Game.Screens.SelectV2 wedgesContainer = new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Margin = new MarginPadding - { - Top = -CORNER_RADIUS_HIDE_OFFSET, - Left = -CORNER_RADIUS_HIDE_OFFSET - }, Spacing = new Vector2(0f, 4f), Direction = FillDirection.Vertical, Children = new Drawable[] From f9a3c57f03b87423e6fb7bbb2d95c471798d91b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 02:29:37 +0900 Subject: [PATCH 06/53] Fix panels flashing white on first display --- osu.Game/Screens/SelectV2/Panel.cs | 46 +++++++++++------------ osu.Game/Screens/SelectV2/PanelBeatmap.cs | 2 + 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Panel.cs b/osu.Game/Screens/SelectV2/Panel.cs index de559be4a9..6e3db2fabd 100644 --- a/osu.Game/Screens/SelectV2/Panel.cs +++ b/osu.Game/Screens/SelectV2/Panel.cs @@ -61,11 +61,10 @@ namespace osu.Game.Screens.SelectV2 public Color4? AccentColour { - get => accentColour; set { accentColour = value; - updateDisplay(); + updateAccentColour(); } } @@ -108,7 +107,7 @@ namespace osu.Game.Screens.SelectV2 backgroundBorder = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Colour = Color4.Black, }, backgroundLayerHorizontalPadding = new Container { @@ -190,7 +189,11 @@ namespace osu.Game.Screens.SelectV2 { base.LoadComplete(); - Expanded.BindValueChanged(_ => updateDisplay(), true); + Expanded.BindValueChanged(_ => + { + updateEdgeEffect(); + updateXOffset(); + }); Selected.BindValueChanged(selected => { @@ -217,6 +220,9 @@ namespace osu.Game.Screens.SelectV2 { base.PrepareForUse(); + updateAccentColour(); + updateXOffset(); + this.FadeIn(DURATION, Easing.OutQuint); } @@ -236,18 +242,20 @@ namespace osu.Game.Screens.SelectV2 return true; } - private void updateDisplay() + private void updateAccentColour() { var backgroundColour = accentColour ?? Color4.White; + + backgroundAccentGradient.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)); + backgroundBorder.Colour = backgroundColour; + + updateEdgeEffect(animated: false); + } + + private void updateEdgeEffect(bool animated = true) + { var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF"); - - backgroundAccentGradient.FadeColour(ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)), DURATION, Easing.OutQuint); - backgroundBorder.FadeColour(backgroundColour, DURATION, Easing.OutQuint); - - TopLevelContent.FadeEdgeEffectTo(Expanded.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), DURATION, Easing.OutQuint); - - updateXOffset(); - updateHover(); + TopLevelContent.FadeEdgeEffectTo(Expanded.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), animated ? DURATION : 0, Easing.OutQuint); } private void updateXOffset() @@ -263,23 +271,15 @@ namespace osu.Game.Screens.SelectV2 TopLevelContent.MoveToX(x, DURATION, Easing.OutQuint); } - private void updateHover() - { - if (IsHovered) - hoverLayer.FadeIn(100, Easing.OutQuint); - else - hoverLayer.FadeOut(1000, Easing.OutQuint); - } - protected override bool OnHover(HoverEvent e) { - updateHover(); + hoverLayer.FadeIn(100, Easing.OutQuint); return true; } protected override void OnHoverLost(HoverLostEvent e) { - updateHover(); + hoverLayer.FadeOut(1000, Easing.OutQuint); base.OnHoverLost(e); } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 190e563c46..8eededd412 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -226,6 +226,8 @@ namespace osu.Game.Screens.SelectV2 // Dirty hack to make sure we don't take up spacing in parent fill flow when not displaying a rank. // I can't find a better way to do this. starRatingDisplay.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; + + AccentColour = starRatingDisplay.DisplayedDifficultyColour; } private void updateKeyCount() From 8c6a414737b303d00a9ae29021dd2a912166d735 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 02:46:21 +0900 Subject: [PATCH 07/53] Make leaderboard scores semi-transparent --- osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index 113894ab8a..213e42282d 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -144,6 +144,7 @@ namespace osu.Game.Screens.SelectV2 { background = new Box { + Alpha = 0.4f, RelativeSizeAxes = Axes.Both, Colour = backgroundColour }, @@ -190,6 +191,7 @@ namespace osu.Game.Screens.SelectV2 { foreground = new Box { + Alpha = 0.4f, RelativeSizeAxes = Axes.Both, Colour = foregroundColour }, From fc66af3b9a250ff3fa6e98c375a142022f3f61df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 02:55:38 +0900 Subject: [PATCH 08/53] Make leaderboard scores more compact --- osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index 213e42282d..dd4e2d4f9c 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.SelectV2 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 float rank_label_width = 40; private const int corner_radius = 10; private const int transition_duration = 200; @@ -314,8 +314,8 @@ namespace osu.Game.Screens.SelectV2 Child = statisticsContainer = new FillFlowContainer { Name = @"Statistics container", - Padding = new MarginPadding { Right = 40 }, - Spacing = new Vector2(25, 0), + Padding = new MarginPadding { Right = 10 }, + Spacing = new Vector2(20, 0), Shear = sheared ? -OsuGame.SHEAR : Vector2.Zero, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -569,13 +569,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 >= 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 >= 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 >= username_min_width + statistics_compact_min_width + expanded_right_content_width) return DisplayMode.Compact; return DisplayMode.Minimal; From 1af39b6b5bd2d6619ca2750e5787bd9e54d166ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 03:05:07 +0900 Subject: [PATCH 09/53] Fix personal best being cut off weirdly --- .../Screens/SelectV2/BeatmapLeaderboardWedge.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index 29affaa9af..1f63e3de9f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.PolygonExtensions; 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.Graphics; @@ -70,7 +69,7 @@ namespace osu.Game.Screens.SelectV2 private readonly IBindable fetchedScores = new Bindable(); - private const float personal_best_height = 80; + private const float personal_best_height = 100; [BackgroundDependencyLoader] private void load() @@ -109,7 +108,10 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.X, Height = personal_best_height, Shear = OsuGame.SHEAR, - Margin = new MarginPadding { Left = -40f }, + Margin = new MarginPadding + { + Left = -40f, + }, CornerRadius = 10f, Masking = true, // push the personal best 1px down to hide masking issues @@ -118,11 +120,7 @@ namespace osu.Game.Screens.SelectV2 Alpha = 0f, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - }, + new WedgeBackground(), new Container { RelativeSizeAxes = Axes.X, From c8389a8683a0e16d43c1e2eafe77e4f81fc26261 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 20:12:32 +0900 Subject: [PATCH 10/53] Adjust panel selection glow and background --- osu.Game/Screens/SelectV2/Panel.cs | 16 ++++++++++------ .../Screens/SelectV2/PanelSetBackground.cs | 18 ++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Panel.cs b/osu.Game/Screens/SelectV2/Panel.cs index 6e3db2fabd..39808e0baf 100644 --- a/osu.Game/Screens/SelectV2/Panel.cs +++ b/osu.Game/Screens/SelectV2/Panel.cs @@ -94,8 +94,8 @@ namespace osu.Game.Screens.SelectV2 EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, - Offset = new Vector2(1f), - Radius = 10, + Hollow = true, + Radius = 2, }, Children = new Drawable[] { @@ -123,6 +123,8 @@ namespace osu.Game.Screens.SelectV2 { RelativeSizeAxes = Axes.Both, }, + // TODO: this is only used by beatmap panels and should NOT be in this class. + // it's wasting fill rate. backgroundAccentGradient = new Box { RelativeSizeAxes = Axes.Both, @@ -157,10 +159,9 @@ namespace osu.Game.Screens.SelectV2 selectionLayer = new Box { Alpha = 0, - Colour = ColourInfo.GradientHorizontal(colours.BlueDark.Opacity(0), colours.BlueDark.Opacity(0.6f)), - Blending = BlendingParameters.Additive, RelativeSizeAxes = Axes.Both, - Width = 0.3f, + Width = 0.6f, + Blending = BlendingParameters.Additive, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, @@ -202,6 +203,7 @@ namespace osu.Game.Screens.SelectV2 else selectionLayer.FadeOut(200, Easing.OutQuint); + updateEdgeEffect(); updateXOffset(); }, true); @@ -249,13 +251,15 @@ namespace osu.Game.Screens.SelectV2 backgroundAccentGradient.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)); backgroundBorder.Colour = backgroundColour; + selectionLayer.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), backgroundColour.Opacity(0.5f)); + updateEdgeEffect(animated: false); } private void updateEdgeEffect(bool animated = true) { var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF"); - TopLevelContent.FadeEdgeEffectTo(Expanded.Value ? edgeEffectColour.Opacity(0.5f) : Color4.Black.Opacity(0.4f), animated ? DURATION : 0, Easing.OutQuint); + TopLevelContent.FadeEdgeEffectTo(Expanded.Value || Selected.Value ? edgeEffectColour.Opacity(0.8f) : Color4.Black.Opacity(0.4f), animated ? DURATION : 0, Easing.OutQuint); } private void updateXOffset() diff --git a/osu.Game/Screens/SelectV2/PanelSetBackground.cs b/osu.Game/Screens/SelectV2/PanelSetBackground.cs index 99dbf90556..dd07be0410 100644 --- a/osu.Game/Screens/SelectV2/PanelSetBackground.cs +++ b/osu.Game/Screens/SelectV2/PanelSetBackground.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -61,34 +62,27 @@ namespace osu.Game.Screens.SelectV2 Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), - Alpha = 0.5f, Children = new[] { // The left half with no gradient applied new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, + Colour = Color4.Black.Opacity(0.5f), Width = 0.4f, }, - // Piecewise-linear gradient with 3 segments to make it appear smoother new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), - Width = 0.05f, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0.3f)), Width = 0.2f, }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), - Width = 0.05f, + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.3f), Color4.Black.Opacity(0.2f)), + // Slightly more than 1.0 in total to account for shear. + Width = 0.45f, }, } }, From 035541bab28ecf7d3ca2811cd67a191ffc9e38ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 20:19:54 +0900 Subject: [PATCH 11/53] Use realtime difficulty colouring for other relevant areas --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 9 ++++----- osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 9 +++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 8eededd412..90de0dd270 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -227,7 +227,10 @@ namespace osu.Game.Screens.SelectV2 // I can't find a better way to do this. starRatingDisplay.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; - AccentColour = starRatingDisplay.DisplayedDifficultyColour; + var diffColour = starRatingDisplay.DisplayedDifficultyColour; + + AccentColour = diffColour; + starCounter.Colour = diffColour; } private void updateKeyCount() @@ -261,10 +264,6 @@ namespace osu.Game.Screens.SelectV2 starCounter.Current = (float)starDifficulty.Stars; difficultyIcon.FadeColour(starDifficulty.Stars > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); - - var starRatingColour = colours.ForStarDifficulty(starDifficulty.Stars); - starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint); - AccentColour = starRatingColour; } public override MenuItem[] ContextMenuItems diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 86f8374088..640cdccb1b 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -273,6 +273,11 @@ namespace osu.Game.Screens.SelectV2 // Dirty hack to make sure we don't take up spacing in parent fill flow when not displaying a rank. // I can't find a better way to do this. starRatingDisplay.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; + + var diffColour = starRatingDisplay.DisplayedDifficultyColour; + + AccentColour = diffColour; + starCounter.Colour = diffColour; } private void updateKeyCount() @@ -306,10 +311,6 @@ namespace osu.Game.Screens.SelectV2 starCounter.Current = (float)starDifficulty.Stars; difficultyIcon.FadeColour(starDifficulty.Stars > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); - - var starRatingColour = colours.ForStarDifficulty(starDifficulty.Stars); - starCounter.FadeColour(starRatingColour, duration, Easing.OutQuint); - AccentColour = colours.ForStarDifficulty(starDifficulty.Stars); } public override MenuItem[] ContextMenuItems From 69b047d72b98e34c6b29c76f782f0034948ecb9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 20:20:09 +0900 Subject: [PATCH 12/53] Highlight beatmap set header when expanded as if it was selected --- osu.Game/Screens/SelectV2/Panel.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Panel.cs b/osu.Game/Screens/SelectV2/Panel.cs index 39808e0baf..12b9613039 100644 --- a/osu.Game/Screens/SelectV2/Panel.cs +++ b/osu.Game/Screens/SelectV2/Panel.cs @@ -192,18 +192,13 @@ namespace osu.Game.Screens.SelectV2 Expanded.BindValueChanged(_ => { - updateEdgeEffect(); + updateSelectedState(); updateXOffset(); }); - Selected.BindValueChanged(selected => + Selected.BindValueChanged(_ => { - if (selected.NewValue) - selectionLayer.FadeIn(100, Easing.OutQuint); - else - selectionLayer.FadeOut(200, Easing.OutQuint); - - updateEdgeEffect(); + updateSelectedState(); updateXOffset(); }, true); @@ -253,13 +248,20 @@ namespace osu.Game.Screens.SelectV2 selectionLayer.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), backgroundColour.Opacity(0.5f)); - updateEdgeEffect(animated: false); + updateSelectedState(animated: false); } - private void updateEdgeEffect(bool animated = true) + private void updateSelectedState(bool animated = true) { + bool selectedOrExpanded = Expanded.Value || Selected.Value; + var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF"); - TopLevelContent.FadeEdgeEffectTo(Expanded.Value || Selected.Value ? edgeEffectColour.Opacity(0.8f) : Color4.Black.Opacity(0.4f), animated ? DURATION : 0, Easing.OutQuint); + TopLevelContent.FadeEdgeEffectTo(selectedOrExpanded ? edgeEffectColour.Opacity(0.8f) : Color4.Black.Opacity(0.4f), animated ? DURATION : 0, Easing.OutQuint); + + if (selectedOrExpanded) + selectionLayer.FadeIn(100, Easing.OutQuint); + else + selectionLayer.FadeOut(200, Easing.OutQuint); } private void updateXOffset() From 4f38f9f486f5aba02e644dfa7bbf619fffda7b28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 20:31:48 +0900 Subject: [PATCH 13/53] Add very slight background dim --- osu.Game/Screens/SelectV2/SongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 84be157619..1056c2ff71 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -401,6 +401,7 @@ namespace osu.Game.Screens.SelectV2 backgroundModeBeatmap.BlurAmount.Value = 0; backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.1f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); } From 27ce54960626ab80b9e3b7bfb425d5a362353b21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 20:43:04 +0900 Subject: [PATCH 14/53] Also apply realtime colour updates to icon --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 22 ++++++++----------- .../SelectV2/PanelBeatmapStandalone.cs | 22 ++++++++----------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 90de0dd270..31cebbd152 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -210,7 +210,13 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); - starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true); + starDifficultyBindable.BindValueChanged(_ => + { + var starDifficulty = starDifficultyBindable?.Value ?? default; + + starRatingDisplay.Current.Value = starDifficulty; + starCounter.Current = (float)starDifficulty.Stars; + }, true); } protected override void Update() @@ -231,6 +237,8 @@ namespace osu.Game.Screens.SelectV2 AccentColour = diffColour; starCounter.Colour = diffColour; + + difficultyIcon.Colour = starRatingDisplay.DisplayedStars.Value > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5; } private void updateKeyCount() @@ -254,18 +262,6 @@ namespace osu.Game.Screens.SelectV2 keyCountText.Alpha = 0; } - private void updateDisplay() - { - const float duration = 500; - - var starDifficulty = starDifficultyBindable?.Value ?? default; - - starRatingDisplay.Current.Value = starDifficulty; - starCounter.Current = (float)starDifficulty.Stars; - - difficultyIcon.FadeColour(starDifficulty.Stars > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); - } - public override MenuItem[] ContextMenuItems { get diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 640cdccb1b..ce9513a8a1 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -257,7 +257,13 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); - starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true); + starDifficultyBindable.BindValueChanged(_ => + { + var starDifficulty = starDifficultyBindable?.Value ?? default; + + starRatingDisplay.Current.Value = starDifficulty; + starCounter.Current = (float)starDifficulty.Stars; + }, true); } protected override void Update() @@ -278,6 +284,8 @@ namespace osu.Game.Screens.SelectV2 AccentColour = diffColour; starCounter.Colour = diffColour; + + difficultyIcon.Colour = starRatingDisplay.DisplayedStars.Value > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5; } private void updateKeyCount() @@ -301,18 +309,6 @@ namespace osu.Game.Screens.SelectV2 keyCountText.Alpha = 0; } - private void updateDisplay() - { - const float duration = 500; - - var starDifficulty = starDifficultyBindable?.Value ?? default; - - starRatingDisplay.Current.Value = starDifficulty; - starCounter.Current = (float)starDifficulty.Stars; - - difficultyIcon.FadeColour(starDifficulty.Stars > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5, duration, Easing.OutQuint); - } - public override MenuItem[] ContextMenuItems { get From 66e2c5c287ea90b291bcce4ba0729ff39e622ab9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 May 2025 20:50:50 +0900 Subject: [PATCH 15/53] Fix oversight in date handling --- osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 1174e172e1..7d449661ab 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.SelectV2 if (BeatmapSetsGroupedTogether) date = aggregateMax(b, static b => (b.LastPlayed ?? DateTimeOffset.MinValue)); - if (date == null) + if (date == null || date == DateTimeOffset.MinValue) return new GroupDefinition(int.MaxValue, "Never"); return defineGroupByDate(date.Value); From 8a3d97903210c9850402b9ff2b90e96af8bc5f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 May 2025 14:16:37 +0200 Subject: [PATCH 16/53] SongSelectV2: Read/write last active tab in details area from/to local configuration --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Screens/Select/BeatmapDetailTab.cs | 38 +++++++++ .../Screens/Select/PlayBeatmapDetailArea.cs | 42 ++++------ .../SelectV2/BeatmapDetailsArea_Header.cs | 83 ++++++++++++++++++- 4 files changed, 137 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Screens/Select/BeatmapDetailTab.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8f6fc214e1..c815b2f9a9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -40,7 +40,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Ruleset, string.Empty); SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString()); - SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Local); + SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local); SetDefault(OsuSetting.BeatmapDetailModsFilter, false); SetDefault(OsuSetting.ShowConvertedBeatmaps, true); diff --git a/osu.Game/Screens/Select/BeatmapDetailTab.cs b/osu.Game/Screens/Select/BeatmapDetailTab.cs new file mode 100644 index 0000000000..cd219a4830 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapDetailTab.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Select +{ + public enum BeatmapDetailTab + { + /// + /// Beatmap details. + /// + Details, + + /// + /// Local leaderboards. + /// + Local, + + /// + /// Country leaderboards. + /// + Country, + + /// + /// Global leaderboards. + /// + Global, + + /// + /// Friend leaderboards. + /// + Friends, + + /// + /// Team leaderboards. + /// + Team + } +} diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index 5b62d5e8d7..ae318de754 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select } } - private Bindable selectedTab; + private Bindable selectedTab; private Bindable selectedModsFilter; @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab); + selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab); selectedModsFilter = config.GetBindable(OsuSetting.BeatmapDetailModsFilter); selectedTab.BindValueChanged(tab => CurrentTab.Value = getTabItemFromTabType(tab.NewValue), true); @@ -86,26 +86,26 @@ namespace osu.Game.Screens.Select new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Team), }).ToArray(); - private BeatmapDetailAreaTabItem getTabItemFromTabType(TabType type) + private BeatmapDetailAreaTabItem getTabItemFromTabType(BeatmapDetailTab type) { switch (type) { - case TabType.Details: + case BeatmapDetailTab.Details: return new BeatmapDetailAreaDetailTabItem(); - case TabType.Local: + case BeatmapDetailTab.Local: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local); - case TabType.Global: + case BeatmapDetailTab.Global: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global); - case TabType.Country: + case BeatmapDetailTab.Country: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country); - case TabType.Friends: + case BeatmapDetailTab.Friends: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend); - case TabType.Team: + case BeatmapDetailTab.Team: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Team); default: @@ -113,30 +113,30 @@ namespace osu.Game.Screens.Select } } - private TabType getTabTypeFromTabItem(BeatmapDetailAreaTabItem item) + private BeatmapDetailTab getTabTypeFromTabItem(BeatmapDetailAreaTabItem item) { switch (item) { case BeatmapDetailAreaDetailTabItem: - return TabType.Details; + return BeatmapDetailTab.Details; case BeatmapDetailAreaLeaderboardTabItem leaderboardTab: switch (leaderboardTab.Scope) { case BeatmapLeaderboardScope.Local: - return TabType.Local; + return BeatmapDetailTab.Local; case BeatmapLeaderboardScope.Country: - return TabType.Country; + return BeatmapDetailTab.Country; case BeatmapLeaderboardScope.Global: - return TabType.Global; + return BeatmapDetailTab.Global; case BeatmapLeaderboardScope.Friend: - return TabType.Friends; + return BeatmapDetailTab.Friends; case BeatmapLeaderboardScope.Team: - return TabType.Team; + return BeatmapDetailTab.Team; default: throw new ArgumentOutOfRangeException(nameof(item)); @@ -146,15 +146,5 @@ namespace osu.Game.Screens.Select throw new ArgumentOutOfRangeException(nameof(item)); } } - - public enum TabType - { - Details, - Local, - Country, - Global, - Friends, - Team - } } } diff --git a/osu.Game/Screens/SelectV2/BeatmapDetailsArea_Header.cs b/osu.Game/Screens/SelectV2/BeatmapDetailsArea_Header.cs index ee93001b86..76734e110f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapDetailsArea_Header.cs +++ b/osu.Game/Screens/SelectV2/BeatmapDetailsArea_Header.cs @@ -7,8 +7,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Screens.Select; using osu.Game.Screens.Select.Leaderboards; using osuTK; @@ -28,10 +30,12 @@ namespace osu.Game.Screens.SelectV2 public IBindable Scope => scopeDropdown.Current; + private readonly Bindable configDetailTab = new Bindable(); + public IBindable FilterBySelectedMods => selectedModsToggle.Active; [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { InternalChildren = new Drawable[] { @@ -98,18 +102,95 @@ namespace osu.Game.Screens.SelectV2 }, }, }; + + config.BindWith(OsuSetting.BeatmapDetailTab, configDetailTab); + config.BindWith(OsuSetting.BeatmapDetailModsFilter, selectedModsToggle.Active); } protected override void LoadComplete() { base.LoadComplete(); + scopeDropdown.Current.Value = tryMapDetailTabToLeaderboardScope(configDetailTab.Value) ?? scopeDropdown.Current.Value; + scopeDropdown.Current.BindValueChanged(_ => updateConfigDetailTab()); + + tabControl.Current.Value = configDetailTab.Value == BeatmapDetailTab.Details ? Selection.Details : Selection.Ranking; tabControl.Current.BindValueChanged(v => { leaderboardControls.FadeTo(v.NewValue == Selection.Ranking ? 1 : 0, 300, Easing.OutQuint); + updateConfigDetailTab(); }, true); } + #region Reading / writing state from / to configuration + + private void updateConfigDetailTab() + { + switch (tabControl.Current.Value) + { + case Selection.Details: + configDetailTab.Value = BeatmapDetailTab.Details; + return; + + case Selection.Ranking: + configDetailTab.Value = mapLeaderboardScopeToDetailTab(scopeDropdown.Current.Value); + return; + + default: + throw new ArgumentOutOfRangeException(nameof(tabControl.Current.Value), tabControl.Current.Value, null); + } + } + + private static BeatmapLeaderboardScope? tryMapDetailTabToLeaderboardScope(BeatmapDetailTab tab) + { + switch (tab) + { + case BeatmapDetailTab.Local: + return BeatmapLeaderboardScope.Local; + + case BeatmapDetailTab.Country: + return BeatmapLeaderboardScope.Country; + + case BeatmapDetailTab.Global: + return BeatmapLeaderboardScope.Global; + + case BeatmapDetailTab.Friends: + return BeatmapLeaderboardScope.Friend; + + case BeatmapDetailTab.Team: + return BeatmapLeaderboardScope.Team; + + default: + return null; + } + } + + private static BeatmapDetailTab mapLeaderboardScopeToDetailTab(BeatmapLeaderboardScope scope) + { + switch (scope) + { + case BeatmapLeaderboardScope.Local: + return BeatmapDetailTab.Local; + + case BeatmapLeaderboardScope.Country: + return BeatmapDetailTab.Country; + + case BeatmapLeaderboardScope.Global: + return BeatmapDetailTab.Global; + + case BeatmapLeaderboardScope.Friend: + return BeatmapDetailTab.Friends; + + case BeatmapLeaderboardScope.Team: + return BeatmapDetailTab.Team; + + default: + throw new ArgumentOutOfRangeException(nameof(scope), scope, null); + } + } + + #endregion + public enum Selection { Details, From 23f2c6d1fe665c396ccc602f39d9357bb0f1a4c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 00:49:29 +0900 Subject: [PATCH 17/53] Add triangles to beatmap difficulty panels and move accent layer local --- osu.Game/Screens/SelectV2/Panel.cs | 9 +---- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 40 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Panel.cs b/osu.Game/Screens/SelectV2/Panel.cs index 12b9613039..8ccc930a2d 100644 --- a/osu.Game/Screens/SelectV2/Panel.cs +++ b/osu.Game/Screens/SelectV2/Panel.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.SelectV2 private Box backgroundBorder = null!; private Box backgroundGradient = null!; - private Box backgroundAccentGradient = null!; private Container backgroundLayerHorizontalPadding = null!; private Container backgroundContainer = null!; private Container iconContainer = null!; @@ -61,6 +60,7 @@ namespace osu.Game.Screens.SelectV2 public Color4? AccentColour { + get => accentColour; set { accentColour = value; @@ -123,12 +123,6 @@ namespace osu.Game.Screens.SelectV2 { RelativeSizeAxes = Axes.Both, }, - // TODO: this is only used by beatmap panels and should NOT be in this class. - // it's wasting fill rate. - backgroundAccentGradient = new Box - { - RelativeSizeAxes = Axes.Both, - }, backgroundContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -243,7 +237,6 @@ namespace osu.Game.Screens.SelectV2 { var backgroundColour = accentColour ?? Color4.White; - backgroundAccentGradient.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0.25f), backgroundColour.Opacity(0f)); backgroundBorder.Colour = backgroundColour; selectionLayer.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), backgroundColour.Opacity(0.5f)); diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 31cebbd152..1d1d37e42f 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -7,12 +7,16 @@ using System.Diagnostics; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -40,6 +44,10 @@ namespace osu.Game.Screens.SelectV2 private IBindable? starDifficultyBindable; private CancellationTokenSource? starDifficultyCancellationSource; + private Box backgroundAccentGradient = null!; + + private TrianglesV2 triangles = null!; + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -83,6 +91,25 @@ namespace osu.Game.Screens.SelectV2 Colour = colourProvider.Background5, }; + Background = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundAccentGradient = new Box + { + RelativeSizeAxes = Axes.Both, + }, + triangles = new TrianglesV2 + { + ScaleAdjust = 1.2f, + Thickness = 0.01f, + Velocity = 0.3f, + RelativeSizeAxes = Axes.Both, + }, + } + }; + Content.Children = new[] { new FillFlowContainer @@ -235,10 +262,17 @@ namespace osu.Game.Screens.SelectV2 var diffColour = starRatingDisplay.DisplayedDifficultyColour; - AccentColour = diffColour; - starCounter.Colour = diffColour; + if (AccentColour != diffColour) + { + AccentColour = diffColour; + starCounter.Colour = diffColour; - difficultyIcon.Colour = starRatingDisplay.DisplayedStars.Value > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5; + backgroundAccentGradient.Colour = ColourInfo.GradientHorizontal(diffColour.Opacity(0.25f), diffColour.Opacity(0f)); + + difficultyIcon.Colour = starRatingDisplay.DisplayedStars.Value > OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider.Background5; + + triangles.Colour = ColourInfo.GradientVertical(diffColour.Opacity(0.25f), diffColour.Opacity(0f)); + } } private void updateKeyCount() From 02610c5afe6215f790caacaebaf5a18a32268cfa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 01:12:11 +0900 Subject: [PATCH 18/53] Adjust panel x offsets to match user expectations better --- osu.Game/Screens/SelectV2/Panel.cs | 11 ++++++++--- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 5 +++++ osu.Game/Screens/SelectV2/SongSelect.cs | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Panel.cs b/osu.Game/Screens/SelectV2/Panel.cs index 8ccc930a2d..f17567f9ba 100644 --- a/osu.Game/Screens/SelectV2/Panel.cs +++ b/osu.Game/Screens/SelectV2/Panel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.SelectV2 { private const float corner_radius = 10; - private const float active_x_offset = 50f; + private const float active_x_offset = 25f; protected const float DURATION = 400; @@ -262,10 +262,15 @@ namespace osu.Game.Screens.SelectV2 float x = PanelXOffset + corner_radius; if (!Expanded.Value && !Selected.Value) - x += active_x_offset; + { + if (this is PanelBeatmap) + x += active_x_offset * 2; + else + x += active_x_offset * 4; + } if (!KeyboardSelected.Value) - x += active_x_offset * 0.5f; + x += active_x_offset; TopLevelContent.MoveToX(x, DURATION, Easing.OutQuint); } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 1d1d37e42f..df9b2bdb5e 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -66,6 +66,11 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private ISongSelect? songSelect { get; set; } + public PanelBeatmap() + { + PanelXOffset = 60; + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { var inputRectangle = TopLevelContent.DrawRectangle; diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 1056c2ff71..fd00458f28 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.SelectV2 { new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 660), new Dimension(), - new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 580), + new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 620), }, Content = new[] { From 6fbc9e4580300d1c2b6a5cce99683fea9977e8c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 01:33:59 +0900 Subject: [PATCH 19/53] Separate active standalone beatmap panels from the carousel vertically --- osu.Game/Graphics/Carousel/Carousel.cs | 4 ++-- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 3 +++ .../Screens/SelectV2/PanelBeatmapStandalone.cs | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 9da4d0e187..48cfbe4732 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -617,13 +617,13 @@ namespace osu.Game.Graphics.Carousel { var item = carouselItems[i]; - updateItemYPosition(item, ref lastVisible, ref yPos); - if (CheckModelEquality(item.Model, currentKeyboardSelection.Model!)) currentKeyboardSelection = new Selection(currentKeyboardSelection.Model, item, item.CarouselYPosition, i); if (CheckModelEquality(item.Model, currentSelection.Model!)) currentSelection = new Selection(currentSelection.Model, item, item.CarouselYPosition, i); + + updateItemYPosition(item, ref lastVisible, ref yPos); } // If a keyboard selection is currently made, we want to keep the view stable around the selection. diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index e007ae54ce..9d2abdff6f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -64,6 +64,9 @@ namespace osu.Game.Screens.SelectV2 if (grouping.BeatmapSetsGroupedTogether && (top.Model is BeatmapInfo || bottom.Model is BeatmapInfo)) return SPACING; + if (!grouping.BeatmapSetsGroupedTogether && (top == CurrentSelectionItem || bottom == CurrentSelectionItem)) + return SPACING * 2; + return -SPACING; } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index ce9513a8a1..16820d3daf 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -71,6 +71,22 @@ namespace osu.Game.Screens.SelectV2 private OsuSpriteText difficultyText = null!; private OsuSpriteText authorText = null!; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = TopLevelContent.DrawRectangle; + + if (Selected.Value) + { + // Cover the gaps introduced by the spacing between BeatmapPanels so that clicks will not fall through the carousel. + // + // Caveat is that for simplicity, we are covering the full spacing, so panels with frontmost depth will have a slightly + // larger hit target. + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapCarousel.SPACING * 2 }); + } + + return inputRectangle.Contains(TopLevelContent.ToLocalSpace(screenSpacePos)); + } + public PanelBeatmapStandalone() { PanelXOffset = 20; From df51bb2fa6616e2ae0a93be5421785cbd5c347f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 01:38:59 +0900 Subject: [PATCH 20/53] Hide options popover when mods overlay is shown --- osu.Game/Screens/Footer/ScreenFooter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 3907907158..c2765fc33d 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -312,6 +313,8 @@ namespace osu.Game.Screens.Footer private void showOverlay(OverlayContainer overlay) { + this.HidePopover(); + foreach (var o in overlays.Where(o => o != overlay)) o.Hide(); From b1a11061f4f1f8e80cfde07cc4a8e1a492e9aebb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 02:15:36 +0900 Subject: [PATCH 21/53] Adjust filter control copy and spacing to fit text better --- .../SongSelectV2/BeatmapCarouselFilterGroupingTest.cs | 2 +- .../Visual/SongSelectV2/SongSelectTestScene.cs | 2 +- .../Visual/SongSelectV2/TestSceneBeatmapCarousel.cs | 4 ++-- .../SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs | 2 +- .../Visual/UserInterface/TestSceneTabControl.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Screens/Select/Filter/GroupMode.cs | 4 ++-- .../Screens/SelectV2/BeatmapCarouselFilterGrouping.cs | 10 +++++----- osu.Game/Screens/SelectV2/FilterControl.cs | 9 ++++----- 9 files changed, 18 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs index ca2c5d415e..7f34d7a901 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ..beatmap4.Beatmaps ]; - var results = await runGrouping(GroupMode.NoGrouping, beatmapSets); + var results = await runGrouping(GroupMode.None, beatmapSets); Assert.That(results.Select(r => r.Model).OfType(), Is.EquivalentTo(beatmapSets)); Assert.That(results.Select(r => r.Model).OfType(), Is.EquivalentTo(allBeatmaps)); assertTotal(results, beatmapSets.Count + allBeatmaps.Length); diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs index c7c56f30f4..75996fe158 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectedMods.SetDefault(); Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title); - Config.SetValue(OsuSetting.SongSelectGroupMode, GroupMode.NoGrouping); + Config.SetValue(OsuSetting.SongSelectGroupMode, GroupMode.None); SongSelect = null!; }); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs index 56351eed97..05f38d2bc8 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Explicit] public void TestSorting() { - SortAndGroupBy(SortMode.Artist, GroupMode.NoGrouping); + SortAndGroupBy(SortMode.Artist, GroupMode.None); SortAndGroupBy(SortMode.Difficulty, GroupMode.Difficulty); SortAndGroupBy(SortMode.Artist, GroupMode.Artist); } @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public void TestLoadingDisplay() { AddStep("induce slow filtering", () => Carousel.FilterDelay = 2000); - SortAndGroupBy(SortMode.Artist, GroupMode.NoGrouping); + SortAndGroupBy(SortMode.Artist, GroupMode.None); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs index e72a373d63..9d5d5ddedb 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddBeatmaps(2, 3); WaitForDrawablePanels(); - SortAndGroupBy(SortMode.Difficulty, GroupMode.NoGrouping); + SortAndGroupBy(SortMode.Difficulty, GroupMode.None); WaitForFiltering(); AddUntilStep("standalone panels displayed", () => GetVisiblePanels().Any()); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs index bf75d07c2c..49eb1f092c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.UserInterface Position = new Vector2(275, 5) }); - filter.PinItem(GroupMode.NoGrouping); + filter.PinItem(GroupMode.None); filter.PinItem(GroupMode.LastPlayed); filter.Current.ValueChanged += grouping => diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8f6fc214e1..2fb67f2266 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -47,7 +47,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); SetDefault(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1); - SetDefault(OsuSetting.SongSelectGroupMode, GroupMode.NoGrouping); + SetDefault(OsuSetting.SongSelectGroupMode, GroupMode.None); SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title); SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs index 9f693177d8..3f8ecba86a 100644 --- a/osu.Game/Screens/Select/Filter/GroupMode.cs +++ b/osu.Game/Screens/Select/Filter/GroupMode.cs @@ -7,8 +7,8 @@ namespace osu.Game.Screens.Select.Filter { public enum GroupMode { - [Description("No Grouping")] - NoGrouping, + [Description("None")] + None, [Description("Artist")] Artist, diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 7d449661ab..78bff0e3c0 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -139,7 +139,7 @@ namespace osu.Game.Screens.SelectV2 { switch (criteria.Group) { - case GroupMode.NoGrouping: + case GroupMode.None: return new List { new GroupMapping(null, items) }; case GroupMode.Artist: @@ -201,19 +201,19 @@ namespace osu.Game.Screens.SelectV2 case GroupMode.Collections: // TODO: needs implementation - goto case GroupMode.NoGrouping; + goto case GroupMode.None; case GroupMode.Favourites: // TODO: needs implementation - goto case GroupMode.NoGrouping; + goto case GroupMode.None; case GroupMode.MyMaps: // TODO: needs implementation - goto case GroupMode.NoGrouping; + goto case GroupMode.None; case GroupMode.RankAchieved: // TODO: needs implementation - goto case GroupMode.NoGrouping; + goto case GroupMode.None; default: throw new ArgumentOutOfRangeException(); diff --git a/osu.Game/Screens/SelectV2/FilterControl.cs b/osu.Game/Screens/SelectV2/FilterControl.cs index 8b360688fa..05429c2c12 100644 --- a/osu.Game/Screens/SelectV2/FilterControl.cs +++ b/osu.Game/Screens/SelectV2/FilterControl.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; -using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; @@ -142,9 +141,9 @@ namespace osu.Game.Screens.SelectV2 RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, ColumnDimensions = new[] { - new Dimension(maxSize: 210), + new Dimension(maxSize: 180), new Dimension(GridSizeMode.Absolute, 5), - new Dimension(maxSize: 230), + new Dimension(maxSize: 180), new Dimension(GridSizeMode.Absolute, 5), new Dimension(), }, @@ -152,14 +151,14 @@ namespace osu.Game.Screens.SelectV2 { new[] { - sortDropdown = new ShearedDropdown(SortStrings.Default) + sortDropdown = new ShearedDropdown("Sort") { RelativeSizeAxes = Axes.X, Items = Enum.GetValues(), }, Empty(), // todo: pending localisation - groupDropdown = new ShearedDropdown("Group by") + groupDropdown = new ShearedDropdown("Group") { RelativeSizeAxes = Axes.X, Items = Enum.GetValues(), From fedc416b173ae9ebb75ea7da5786e1c140a809ef Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 30 May 2025 12:58:42 +0900 Subject: [PATCH 22/53] Attempt to stabilise hot-reload --- Directory.Build.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index 3acb86ee0c..d7a8289b4e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,6 +3,8 @@ 12.0 enable + + false $(MSBuildThisFileDirectory)app.manifest From 5280973d23f0e284b3cf7efb4cc25b6de76e3a35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 30 May 2025 13:07:58 +0900 Subject: [PATCH 23/53] Disable CA1416 --- Directory.Build.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Directory.Build.props b/Directory.Build.props index d7a8289b4e..580e61dafb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,6 +5,8 @@ enable false + + $(NoWarn);CA1416 $(MSBuildThisFileDirectory)app.manifest From afd0f39cc2a77665ee8301211a76056659beec96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 12:31:03 +0900 Subject: [PATCH 24/53] Fix back-to-front ordering of elements on `PanelBeatmap` --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 56 +++++++++++------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index df9b2bdb5e..d65e1cdfbb 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -126,33 +126,6 @@ namespace osu.Game.Screens.SelectV2 AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - localRank = new PanelLocalRankDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.65f) - }, - starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small, animated: true) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.875f), - }, - starCounter = new StarCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.4f) - } - } - }, new FillFlowContainer { Direction = FillDirection.Horizontal, @@ -181,7 +154,34 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.BottomLeft } } - } + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + localRank = new PanelLocalRankDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.65f) + }, + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small, animated: true) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.875f), + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.4f) + } + } + }, } }, }; From 11878dde5c936251e89d161e8cbf7b1623c70fd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 12:45:56 +0900 Subject: [PATCH 25/53] Fix key count potentially not updating after de-pool --- osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 16820d3daf..08de51c061 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -248,6 +248,7 @@ namespace osu.Game.Screens.SelectV2 difficultyLine.Show(); computeStarRating(); + updateKeyCount(); } protected override void FreeAfterUse() From 31e46ae0d9001758ef84410fe57c82e867259b4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 12:58:01 +0900 Subject: [PATCH 26/53] Move local rank out to the left and further fine-tune metrics --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 126 +++++++------- .../SelectV2/PanelBeatmapStandalone.cs | 158 ++++++++++-------- 2 files changed, 152 insertions(+), 132 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index d65e1cdfbb..29002179b0 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.SelectV2 private PanelLocalRankDisplay localRank = null!; private OsuSpriteText difficultyText = null!; private OsuSpriteText authorText = null!; + private FillFlowContainer mainFill = null!; private IBindable? starDifficultyBindable; private CancellationTokenSource? starDifficultyCancellationSource; @@ -115,75 +116,84 @@ namespace osu.Game.Screens.SelectV2 } }; - Content.Children = new[] + Content.Child = new FillFlowContainer { - new FillFlowContainer + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Spacing = new Vector2(3), + Margin = new MarginPadding { Left = 5 }, + Direction = FillDirection.Horizontal, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Left = 10f }, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + localRank = new PanelLocalRankDisplay { - new FillFlowContainer + Scale = new Vector2(0.8f), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + }, + mainFill = new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Children = new[] + new FillFlowContainer { - keyCountText = new OsuSpriteText + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 2, Bottom = 2 }, + Children = new Drawable[] { - Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - }, - difficultyText = new OsuSpriteText - { - Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 3f }, - }, - authorText = new OsuSpriteText - { - Colour = colourProvider.Content2, - Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft + keyCountText = new OsuSpriteText + { + Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + }, + difficultyText = new OsuSpriteText + { + Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 3f }, + }, + authorText = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft + } } - } - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + }, + new FillFlowContainer { - localRank = new PanelLocalRankDisplay + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.65f) + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small, animated: true) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Scale = new Vector2(0.875f), + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.4f) + } }, - starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small, animated: true) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.875f), - }, - starCounter = new StarCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.4f) - } } - }, + } } - }, + } }; } @@ -263,7 +273,7 @@ namespace osu.Game.Screens.SelectV2 // Dirty hack to make sure we don't take up spacing in parent fill flow when not displaying a rank. // I can't find a better way to do this. - starRatingDisplay.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; + mainFill.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; var diffColour = starRatingDisplay.DisplayedDifficultyColour; diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 08de51c061..3623fa35ec 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -63,13 +63,13 @@ namespace osu.Game.Screens.SelectV2 private BeatmapSetOnlineStatusPill statusPill = null!; private ConstrainedIconContainer difficultyIcon = null!; - private FillFlowContainer difficultyLine = null!; private StarRatingDisplay starRatingDisplay = null!; private StarCounter starCounter = null!; private PanelLocalRankDisplay localRank = null!; private OsuSpriteText keyCountText = null!; private OsuSpriteText difficultyText = null!; private OsuSpriteText authorText = null!; + private FillFlowContainer mainFill = null!; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { @@ -111,93 +111,104 @@ namespace osu.Game.Screens.SelectV2 Content.Child = new FillFlowContainer { + AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Left = 10f }, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), + Margin = new MarginPadding { Left = 5 }, + Direction = FillDirection.Horizontal, Children = new Drawable[] { - titleText = new OsuSpriteText + localRank = new PanelLocalRankDisplay { - Font = OsuFont.Style.Heading2.With(typeface: Typeface.TorusAlternate, weight: FontWeight.Bold), + Scale = new Vector2(0.8f), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, }, - artistText = new OsuSpriteText + mainFill = new FillFlowContainer { - Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), - Padding = new MarginPadding { Top = -2 }, - }, - difficultyLine = new FillFlowContainer - { - Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 4 }, Children = new Drawable[] { - statusPill = new BeatmapSetOnlineStatusPill + titleText = new OsuSpriteText { - Animated = false, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - TextSize = OsuFont.Style.Caption2.Size, - Margin = new MarginPadding { Right = 5f }, + Font = OsuFont.Style.Heading2.With(typeface: Typeface.TorusAlternate, weight: FontWeight.Bold), }, - updateButton = new PanelUpdateBeatmapButton + artistText = new OsuSpriteText { - Scale = new Vector2(0.7f), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5f, Top = -2f }, - }, - keyCountText = new OsuSpriteText - { - Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - }, - difficultyText = new OsuSpriteText - { - Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Right = 3f }, - }, - authorText = new OsuSpriteText - { - Colour = colourProvider.Content2, Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft + Padding = new MarginPadding { Top = -2 }, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 2, Bottom = 2 }, + Children = new Drawable[] + { + statusPill = new BeatmapSetOnlineStatusPill + { + Animated = false, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + TextSize = OsuFont.Style.Caption2.Size, + Margin = new MarginPadding { Right = 4f }, + }, + updateButton = new PanelUpdateBeatmapButton + { + Scale = new Vector2(0.8f), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 4f, Bottom = -1f }, + }, + keyCountText = new OsuSpriteText + { + Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Alpha = 0, + }, + difficultyText = new OsuSpriteText + { + Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 3f }, + }, + authorText = new OsuSpriteText + { + Colour = colourProvider.Content2, + Font = OsuFont.Style.Caption1.With(weight: FontWeight.SemiBold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft + } + } + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3), + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small, animated: true) + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Scale = new Vector2(0.875f), + }, + starCounter = new StarCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.4f) + } + }, } } - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3), - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - localRank = new PanelLocalRankDisplay - { - Scale = new Vector2(0.65f), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - }, - starRatingDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Small, animated: true) - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Scale = new Vector2(0.875f), - }, - starCounter = new StarCounter - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.4f) - } - }, } } }; @@ -245,7 +256,6 @@ namespace osu.Game.Screens.SelectV2 localRank.Beatmap = beatmap; difficultyText.Text = beatmap.DifficultyName; authorText.Text = BeatmapsetsStrings.ShowDetailsMappedBy(beatmap.Metadata.Author.Username); - difficultyLine.Show(); computeStarRating(); updateKeyCount(); @@ -295,7 +305,7 @@ namespace osu.Game.Screens.SelectV2 // Dirty hack to make sure we don't take up spacing in parent fill flow when not displaying a rank. // I can't find a better way to do this. - starRatingDisplay.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; + mainFill.Margin = new MarginPadding { Left = 1 / starRatingDisplay.Scale.X * (localRank.HasRank ? 0 : -3) }; var diffColour = starRatingDisplay.DisplayedDifficultyColour; From 53d3817e79df028697d2087dc5124a7113b448b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 13:12:37 +0900 Subject: [PATCH 27/53] Adjust ruleset icon sizing and padding --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 4 ++-- osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 29002179b0..d1ee4d341f 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -92,8 +92,8 @@ namespace osu.Game.Screens.SelectV2 Icon = difficultyIcon = new ConstrainedIconContainer { - Size = new Vector2(16f), - Margin = new MarginPadding { Horizontal = 5f }, + Size = new Vector2(9f), + Margin = new MarginPadding { Horizontal = 1.5f }, Colour = colourProvider.Background5, }; diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 3623fa35ec..d68f2041f3 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -99,8 +99,8 @@ namespace osu.Game.Screens.SelectV2 Icon = difficultyIcon = new ConstrainedIconContainer { - Size = new Vector2(16), - Margin = new MarginPadding { Horizontal = 5f }, + Size = new Vector2(12), + Margin = new MarginPadding { Horizontal = 3f }, Colour = colourProvider.Background5, }; From 5943bd4388ef7e5b6364289a076837f686878854 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 13:16:59 +0900 Subject: [PATCH 28/53] Change song select v2 colour scheme to `Blue` --- osu.Game/Screens/Footer/ScreenFooter.cs | 7 ++++++- osu.Game/Screens/SelectV2/SongSelect.cs | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index c2765fc33d..db894e0b49 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -50,8 +50,13 @@ namespace osu.Game.Screens.Footer private Container hiddenButtonsContainer = null!; private LogoTrackingContainer logoTrackingContainer = null!; + // TODO: This has some weird update logic local in this class, but it only works for overlay containers. + // This is not what we want. The footer is to be displayed on *screens* with different colour schemes. + // It needs to update on screen switch. + // + // For now it's locked to Blue to match song select (the most prominent usage). [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); public ScreenFooter(BackReceptor? receptor = null) { diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index fd00458f28..810d0ed765 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -70,15 +70,20 @@ namespace osu.Game.Screens.SelectV2 /// protected bool ControlGlobalMusic { get; init; } = true; - private readonly ModSelectOverlay modSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Aquamarine) + // Colour scheme for mod overlay is left as default (green) to match mods button. + // Not sure about this, but we'll iterate based on feedback. + private readonly ModSelectOverlay modSelectOverlay = new UserModSelectOverlay { ShowPresets = true, }; private ModSpeedHotkeyHandler modSpeedHotkeyHandler = null!; + // Blue is the most neutral choice, so I'm using that for now. + // Purple makes the most sense to match the "gameplay" flow, but it's a bit too strong for the current design. + // TODO: Colour scheme choice should probably be customisable by the user. [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); private BeatmapCarousel carousel = null!; From d179d330bc7115214f0be5e1b67f7a8fe89cc9ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 13:40:32 +0900 Subject: [PATCH 29/53] Add gradient behind carousel to make placeholder easier to see --- osu.Game/Screens/SelectV2/SongSelect.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 810d0ed765..a6a3cfca08 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -201,8 +201,13 @@ namespace osu.Game.Screens.SelectV2 new Container { RelativeSizeAxes = Axes.Both, - Children = new CompositeDrawable[] + Children = new Drawable[] { + new Box + { + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.0f), Color4.Black.Opacity(0.5f)), + RelativeSizeAxes = Axes.Both, + }, new Container { RelativeSizeAxes = Axes.Both, From 64328ffc27471190983f6f8611b5f8a0c5b278cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 13:41:12 +0900 Subject: [PATCH 30/53] Allow resetting search terms from placeholder --- osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs | 15 +++++++++++++++ osu.Game/Screens/SelectV2/SongSelect.cs | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs b/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs index 8caa559550..994f074687 100644 --- a/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,6 +21,8 @@ namespace osu.Game.Screens.SelectV2 { public partial class NoResultsPlaceholder : VisibilityContainer { + public Action? RequestClearFilterText { get; init; } + private FilterCriteria? filter; private LinkFlowContainer textFlow = null!; @@ -131,6 +134,18 @@ namespace osu.Game.Screens.SelectV2 textFlow.AddParagraph("No beatmaps match your filter criteria!"); textFlow.AddParagraph(string.Empty); + if (!string.IsNullOrEmpty(filter?.SearchText)) + { + addBulletPoint(); + textFlow.AddText("Try "); + textFlow.AddLink("clearing", () => + { + RequestClearFilterText?.Invoke(); + }); + + textFlow.AddText(" your current search criteria."); + } + if (filter?.UserStarDifficulty.HasFilter == true) { addBulletPoint(); diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index a6a3cfca08..bd8fe76268 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -228,7 +228,10 @@ namespace osu.Game.Screens.SelectV2 RequestRecommendedSelection = selectRecommendedBeatmap, NewItemsPresented = newItemsPresented, }, - noResultsPlaceholder = new NoResultsPlaceholder(), + noResultsPlaceholder = new NoResultsPlaceholder + { + RequestClearFilterText = () => filterControl.Search(string.Empty) + } } }, filterControl = new FilterControl From 1402feba56af5a0a69a98b5110e4bb21e3bebf25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 13:44:12 +0900 Subject: [PATCH 31/53] Update placeholder design slightly --- .../Screens/SelectV2/NoResultsPlaceholder.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs b/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs index 994f074687..46f8859255 100644 --- a/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/SelectV2/NoResultsPlaceholder.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.SelectV2 private LinkFlowContainer textFlow = null!; + private SpriteIcon icon = null!; + [Resolved] private BeatmapManager beatmaps { get; set; } = null!; @@ -53,8 +55,7 @@ namespace osu.Game.Screens.SelectV2 [BackgroundDependencyLoader] private void load() { - Width = 400; - AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -64,11 +65,13 @@ namespace osu.Game.Screens.SelectV2 new FillFlowContainer { Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new SpriteIcon + icon = new SpriteIcon { Icon = FontAwesome.Solid.Ghost, Anchor = Anchor.TopCentre, @@ -81,7 +84,7 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = OsuFont.Style.Title, - Text = "No beatmaps found" + Text = "No matching beatmaps" }, textFlow = new LinkFlowContainer { @@ -118,6 +121,9 @@ namespace osu.Game.Screens.SelectV2 this.ScaleTo(0.9f) .ScaleTo(1f, 1000, Easing.OutQuint); + icon.ScaleTo(new Vector2(-1, 1)) + .ScaleTo(new Vector2(1, 1), 500, Easing.InOutSine); + textFlow.FadeInFromZero(800, Easing.OutQuint); textFlow.Clear(); From 9721b0a94c607874e580d256fc15754b71cab137 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 14:01:39 +0900 Subject: [PATCH 32/53] Tidy up star difficulty transfer logic Was going to try and fix the stuttering/glitching when changing mods / adjusting star rating but it's for another day. This has no functional change, just code quality. --- .../SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs | 2 +- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 8 +++----- osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 8 +++----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs index 8362f5b6a7..734d768241 100644 --- a/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs +++ b/osu.Game/Screens/SelectV2/BeatmapTitleWedge_DifficultyDisplay.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.SelectV2 mapperText.Text = beatmap.Value.Metadata.Author.Username; } - starRatingDisplay.Current = (Bindable)difficultyCache.GetBindableDifficulty(beatmap.Value.BeatmapInfo, cancellationSource.Token, 200); + starRatingDisplay.Current = (Bindable)difficultyCache.GetBindableDifficulty(beatmap.Value.BeatmapInfo, cancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); updateCountStatistics(cancellationSource.Token); updateDifficultyStatistics(); diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index d1ee4d341f..a4a1a1e4cd 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -252,12 +252,10 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); - starDifficultyBindable.BindValueChanged(_ => + starDifficultyBindable.BindValueChanged(starDifficulty => { - var starDifficulty = starDifficultyBindable?.Value ?? default; - - starRatingDisplay.Current.Value = starDifficulty; - starCounter.Current = (float)starDifficulty.Stars; + starRatingDisplay.Current.Value = starDifficulty.NewValue; + starCounter.Current = (float)starDifficulty.NewValue.Stars; }, true); } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index d68f2041f3..266666e3b5 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -284,12 +284,10 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); - starDifficultyBindable.BindValueChanged(_ => + starDifficultyBindable.BindValueChanged(starDifficulty => { - var starDifficulty = starDifficultyBindable?.Value ?? default; - - starRatingDisplay.Current.Value = starDifficulty; - starCounter.Current = (float)starDifficulty.Stars; + starRatingDisplay.Current.Value = starDifficulty.NewValue; + starCounter.Current = (float)starDifficulty.NewValue.Stars; }, true); } From 6773755a5d405510b11fa7eaf628566b21ab7cce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 14:20:42 +0900 Subject: [PATCH 33/53] Add gradient fade when scrolling leaderboard --- .../SelectV2/BeatmapLeaderboardWedge.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index 1f63e3de9f..f7770838d0 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -3,12 +3,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.PolygonExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; @@ -26,6 +29,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.SelectV2 { @@ -350,6 +354,59 @@ namespace osu.Game.Screens.SelectV2 placeholder.FadeInFromZero(300, Easing.OutQuint); } + #region Fade handling + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + const int height = BeatmapLeaderboardScore.HEIGHT; + + float fadeBottom = (float)(scoresScroll.Current + scoresScroll.DrawHeight); + float fadeTop = (float)(scoresScroll.Current); + + if (!scoresScroll.IsScrolledToStart()) + fadeTop += height; + + foreach (var c in scoresContainer) + { + float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scoresContainer).Y; + float bottomY = topY + height; + + bool requireBottomFade = bottomY >= fadeBottom; + bool requireTopFade = topY < fadeTop; + + if (!requireBottomFade && !requireTopFade) + { + c.Colour = Color4.White; + continue; + } + + if (topY > fadeBottom + height || bottomY < fadeTop - height) + { + c.Colour = Color4.Transparent; + continue; + } + + if (requireBottomFade) + { + c.Colour = ColourInfo.GradientVertical( + Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / height, 1)), + Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / height, 1))); + } + else + { + Debug.Assert(requireTopFade); + + c.Colour = ColourInfo.GradientVertical( + Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / height, 1)), + Color4.White.Opacity(Math.Min(1 - (fadeTop - bottomY) / height, 1))); + } + } + } + + #endregion + private Placeholder? getPlaceholderFor(LeaderboardState state) { switch (state) From 574b0eb686f503f5a1a154575ab34553b43b1cb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 14:26:47 +0900 Subject: [PATCH 34/53] Inflate leaderboard scores input area to avoid misclicks in gaps --- osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs | 9 +++++++++ osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs index dd4e2d4f9c..6a810a83b4 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardScore.cs @@ -117,6 +117,15 @@ namespace osu.Game.Screens.SelectV2 private readonly bool sheared; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + var inputRectangle = DrawRectangle; + + inputRectangle = inputRectangle.Inflate(new MarginPadding { Vertical = BeatmapLeaderboardWedge.SPACING_BETWEEN_SCORES / 2 }); + + return inputRectangle.Contains(ToLocalSpace(screenSpacePos)); + } + public BeatmapLeaderboardScore(ScoreInfo score, bool sheared = true) { this.score = score; diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index f7770838d0..e3d52adef5 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.SelectV2 { public partial class BeatmapLeaderboardWedge : VisibilityContainer { + public const float SPACING_BETWEEN_SCORES = 4; + public IBindable Scope { get; } = new Bindable(); public IBindable FilterBySelectedMods { get; } = new BindableBool(); @@ -258,10 +260,10 @@ namespace osu.Game.Screens.SelectV2 foreach (var d in loadedScores) { - d.Y = (BeatmapLeaderboardScore.HEIGHT + 4f) * i; + d.Y = (BeatmapLeaderboardScore.HEIGHT + SPACING_BETWEEN_SCORES) * i; // 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). + // shear applied, but still need the `BeatmapLeaderboardScore` to be in "sheared" mode (see ctor). d.Shear = Vector2.Zero; scoresContainer.Add(d); From 2bbd1ee38463c4ea32138f5b309d955e07bb2f93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 15:04:41 +0900 Subject: [PATCH 35/53] SongSelectV2: Add back ability to manage collections from beatmap / set panel context menus --- .../SelectV2/FooterButtonOptions_Popover.cs | 4 ++ osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 65 +++++++++++++++++++ osu.Game/Screens/SelectV2/SoloSongSelect.cs | 3 + osu.Game/Screens/SelectV2/SongSelect.cs | 24 +++++++ 4 files changed, 96 insertions(+) diff --git a/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs b/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs index 3031dcb8f7..039020d7c4 100644 --- a/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs +++ b/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs @@ -68,6 +68,10 @@ namespace osu.Game.Screens.SelectV2 foreach (OsuMenuItem item in SongSelect.GetForwardActions(beatmap.BeatmapInfo)) { + // We can't display menus with child items here, so just ignore them. + if (item.Items.Any()) + continue; + if (item is OsuMenuItemSpacer) { buttonFlow.Add(new Container diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index a41c5c75ae..425ca02e5a 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -14,6 +14,8 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Sprites; @@ -188,6 +190,12 @@ namespace osu.Game.Screens.SelectV2 difficultiesDisplay.BeatmapSet = null; } + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + public override MenuItem[] ContextMenuItems { get @@ -215,6 +223,17 @@ namespace osu.Game.Screens.SelectV2 items.Add(new OsuMenuItemSpacer()); } + var collectionItems = realm.Realm.All() + .OrderBy(c => c.Name) + .AsEnumerable() + .Select(createCollectionMenuItem) + .ToList(); + + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => songSelect?.RestoreAllHidden(beatmapSet))); @@ -222,5 +241,51 @@ namespace osu.Game.Screens.SelectV2 return items.ToArray(); } } + + private MenuItem createCollectionMenuItem(BeatmapCollection collection) + { + var beatmapSet = (BeatmapSetInfo)Item!.Model; + + Debug.Assert(beatmapSet != null); + + TernaryState state; + + int countExisting = beatmapSet.Beatmaps.Count(b => collection.BeatmapMD5Hashes.Contains(b.MD5Hash)); + + if (countExisting == beatmapSet.Beatmaps.Count) + state = TernaryState.True; + else if (countExisting > 0) + state = TernaryState.Indeterminate; + else + state = TernaryState.False; + + var liveCollection = collection.ToLive(realm); + + return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s => + { + liveCollection.PerformWrite(c => + { + foreach (var b in beatmapSet.Beatmaps) + { + switch (s) + { + case TernaryState.True: + if (c.BeatmapMD5Hashes.Contains(b.MD5Hash)) + continue; + + c.BeatmapMD5Hashes.Add(b.MD5Hash); + break; + + case TernaryState.False: + c.BeatmapMD5Hashes.Remove(b.MD5Hash); + break; + } + } + }); + }) + { + State = { Value = state } + }; + } } } diff --git a/osu.Game/Screens/SelectV2/SoloSongSelect.cs b/osu.Game/Screens/SelectV2/SoloSongSelect.cs index ea27ffef37..a136e682c4 100644 --- a/osu.Game/Screens/SelectV2/SoloSongSelect.cs +++ b/osu.Game/Screens/SelectV2/SoloSongSelect.cs @@ -73,6 +73,9 @@ namespace osu.Game.Screens.SelectV2 yield return new OsuMenuItemSpacer(); } + foreach (var i in CreateCollectionMenuActions(beatmap)) + yield return i; + // TODO: replace with "remove from played" button when beatmap is already played. yield return new OsuMenuItem(SongSelectStrings.MarkAsPlayed, MenuItemType.Standard, () => beatmaps.MarkPlayed(beatmap)) { Icon = FontAwesome.Solid.TimesCircle }; yield return new OsuMenuItem(SongSelectStrings.ClearAllLocalScores, MenuItemType.Standard, () => dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap))) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 1056c2ff71..6feaeb0ef2 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -21,6 +21,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -659,6 +660,12 @@ namespace osu.Game.Screens.SelectV2 #region Beatmap management + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } = null!; + public virtual IEnumerable GetForwardActions(BeatmapInfo beatmap) { yield return new OsuMenuItem("Select", MenuItemType.Highlighted, () => SelectAndStart(beatmap)) @@ -675,6 +682,23 @@ namespace osu.Game.Screens.SelectV2 if (beatmap.GetOnlineURL(api, Ruleset.Value) is string url) yield return new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => (game as OsuGame)?.CopyToClipboard(url)); } + + yield return new OsuMenuItemSpacer(); + + foreach (var i in CreateCollectionMenuActions(beatmap)) + yield return i; + } + + protected IEnumerable CreateCollectionMenuActions(BeatmapInfo beatmap) + { + var collectionItems = realm.Realm.All() + .OrderBy(c => c.Name) + .AsEnumerable() + .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); + + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, () => manageCollectionsDialog?.Show())); + + yield return new OsuMenuItem("Collections") { Items = collectionItems }; } public void ManageCollections() => collectionsDialog?.Show(); From 9b77b958e00a49b666a73555e779700a188409e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 15:48:41 +0900 Subject: [PATCH 36/53] Add failing tests showing carousel incorrectly requesting start when traversing single items --- .../SongSelectV2/BeatmapCarouselTestScene.cs | 20 +++++++ .../TestSceneBeatmapCarouselNoGrouping.cs | 52 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index f9f7f3e89c..9c109bc782 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -191,6 +191,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2 protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.Null); protected void CheckHasSelection() => AddAssert("has selection", () => Carousel.CurrentSelection, () => Is.Not.Null); + protected void CheckRequestPresentCount(int expected) => + AddAssert($"check present count is {expected}", () => Carousel.RequestPresentBeatmapCount, () => Is.EqualTo(expected)); + + protected void CheckActivationCount(int expected) => + AddAssert($"check activation count is {expected}", () => Carousel.ActivationCount, () => Is.EqualTo(expected)); + protected void CheckDisplayedBeatmapsCount(int expected) { AddAssert($"{expected} diffs displayed", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected)); @@ -375,6 +381,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public partial class TestBeatmapCarousel : BeatmapCarousel { + public int ActivationCount { get; private set; } + public int RequestPresentBeatmapCount { get; private set; } + public int FilterDelay = 0; public IEnumerable PostFilterBeatmaps = null!; @@ -385,6 +394,17 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public new BeatmapSetInfo? ExpandedBeatmapSet => base.ExpandedBeatmapSet; public new GroupDefinition? ExpandedGroup => base.ExpandedGroup; + public TestBeatmapCarousel() + { + RequestPresentBeatmap = _ => RequestPresentBeatmapCount++; + } + + protected override void HandleItemActivated(CarouselItem item) + { + ActivationCount++; + base.HandleItemActivated(item); + } + protected override async Task> FilterAsync(bool clearExistingPanels = false) { var items = await base.FilterAsync(clearExistingPanels); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs index e72a373d63..d0ecb2c05a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselNoGrouping.cs @@ -190,6 +190,58 @@ namespace osu.Game.Tests.Visual.SongSelectV2 WaitForSelection(4, 0); } + [Test] + public void TestSingleItemTraversal() + { + CheckNoSelection(); + AddBeatmaps(1, 3); + + WaitForSelection(0, 0); + CheckActivationCount(0); + + SelectNextGroup(); + WaitForSelection(0, 0); + + // In the case of a grouped beatmap set, the header gets activated and re-selects the recommended difficulty. + // This is probably fine. + CheckActivationCount(1); + // We don't want it to request present though, which would start gameplay. + CheckRequestPresentCount(0); + + SelectPrevGroup(); + WaitForSelection(0, 0); + + CheckActivationCount(1); + CheckRequestPresentCount(0); + } + + [Test] + public void TestSingleItemTraversal_DifficultySplit() + { + SortBy(SortMode.Difficulty); + + CheckNoSelection(); + AddBeatmaps(1, 1); + + WaitForSelection(0, 0); + CheckActivationCount(0); + + SelectNextGroup(); + WaitForSelection(0, 0); + + // In the case of a grouped beatmap set, the header gets activated and re-selects the recommended difficulty. + // This is probably fine. + CheckActivationCount(0); + // We don't want it to request present though, which would start gameplay. + CheckRequestPresentCount(0); + + SelectPrevGroup(); + WaitForSelection(0, 0); + + CheckActivationCount(0); + CheckRequestPresentCount(0); + } + [Test] public void TestEmptyTraversal() { From b050e025dc3deaa8330ee02e350155033fc08421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 15:33:55 +0900 Subject: [PATCH 37/53] Fix carousel activating selection when only one valid item in list --- osu.Game/Graphics/Carousel/Carousel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 9da4d0e187..625f6b3d38 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -530,6 +530,10 @@ namespace osu.Game.Graphics.Carousel do { newIndex = (newIndex + direction + carouselItems.Count) % carouselItems.Count; + + if (newIndex == originalIndex) + break; + var newItem = carouselItems[newIndex]; if (CheckValidForGroupSelection(newItem)) @@ -537,7 +541,7 @@ namespace osu.Game.Graphics.Carousel HandleItemActivated(newItem); return; } - } while (newIndex != originalIndex); + } while (true); } #endregion From 666d9b153cd583d6ae8915ae4e52832ddc7107aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 16:00:20 +0900 Subject: [PATCH 38/53] Add failing test showing double filter on entering song select --- .../Visual/SongSelectV2/BeatmapCarouselTestScene.cs | 2 +- .../Visual/SongSelectV2/TestSceneSongSelect.cs | 9 +++++++++ osu.Game/Graphics/Carousel/Carousel.cs | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index f9f7f3e89c..da339bbc3e 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -356,7 +356,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 """); createHeader("carousel"); stats.AddParagraph($""" - sorting: {Carousel.IsFiltering} + filtering: {Carousel.IsFiltering} (total {Carousel.FilterCount} times) tracked: {Carousel.ItemsTracked} displayable: {Carousel.DisplayableItems} displayed: {Carousel.VisibleItems} diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index b81484d3da..dcd745395b 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -78,6 +78,15 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddUntilStep("wait for results screen", () => Stack.CurrentScreen is ResultsScreen); } + [Test] + public void TestSingleFilterWhenEntering() + { + ImportBeatmapForRuleset(0); + LoadSongSelect(); + + AddAssert("single filter", () => Carousel.FilterCount, () => Is.EqualTo(1)); + } + [Test] public void TestCookieDoesNothingIfNothingSelected() { diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 9da4d0e187..ce5d015775 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -71,6 +71,11 @@ namespace osu.Game.Graphics.Carousel /// public bool IsFiltering => !filterTask.IsCompleted; + /// + /// The number of times filter operations have been triggered. + /// + internal int FilterCount { get; private set; } + /// /// The number of displayable items currently being tracked (before filtering). /// @@ -187,6 +192,8 @@ namespace osu.Game.Graphics.Carousel /// Whether all existing drawable panels should be reset post filter. protected virtual Task> FilterAsync(bool clearExistingPanels = false) { + FilterCount++; + if (clearExistingPanels) filterReusesPanels.Invalidate(); From bb938be0b6e2d43ced4637d961fa8251bcf4916d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 03:02:33 +0900 Subject: [PATCH 39/53] Fix carousel filtering twice on entering song select --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 17 ++++++++++++++++- osu.Game/Screens/SelectV2/SongSelect.cs | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index e007ae54ce..32e14ad43a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -49,6 +50,8 @@ namespace osu.Game.Screens.SelectV2 private readonly BeatmapCarouselFilterMatching matching; private readonly BeatmapCarouselFilterGrouping grouping; + private bool waitingForInitialCriteria; + /// /// Total number of beatmap difficulties displayed with the filter. /// @@ -67,8 +70,10 @@ namespace osu.Game.Screens.SelectV2 return -SPACING; } - public BeatmapCarousel() + public BeatmapCarousel(bool waitForInitialCriteria = false) { + waitingForInitialCriteria = waitForInitialCriteria; + DebounceDelay = 100; DistanceOffscreenToPreload = 100; @@ -473,6 +478,8 @@ namespace osu.Game.Screens.SelectV2 public void Filter(FilterCriteria criteria) { + waitingForInitialCriteria = false; + bool resetDisplay = grouping.BeatmapSetsGroupedTogether != BeatmapCarouselFilterGrouping.ShouldGroupBeatmapsTogether(criteria); Criteria = criteria; @@ -493,6 +500,14 @@ namespace osu.Game.Screens.SelectV2 })); } + protected override Task> FilterAsync(bool clearExistingPanels = false) + { + if (waitingForInitialCriteria) + return Task.FromResult(Enumerable.Empty()); + + return base.FilterAsync(clearExistingPanels); + } + #endregion #region Drawable pooling diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 1056c2ff71..59dae1f28a 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -208,7 +208,7 @@ namespace osu.Game.Screens.SelectV2 }, Children = new Drawable[] { - carousel = new BeatmapCarousel + carousel = new BeatmapCarousel(waitForInitialCriteria: true) { BleedTop = FilterControl.HEIGHT_FROM_SCREEN_TOP + 5, BleedBottom = ScreenFooter.HEIGHT + 5, From 017cef42553cf9b8800e1e8793bf0fc91602fd68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 16:27:19 +0900 Subject: [PATCH 40/53] Separate out expanded/selected set and difficulty panels with extra padding --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 23 +++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 9d2abdff6f..6ee780b603 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -60,12 +60,25 @@ namespace osu.Game.Screens.SelectV2 if ((top.Model is GroupDefinition) ^ (bottom.Model is GroupDefinition)) return SPACING * 2; - // Beatmap difficulty panels do not overlap with themselves or any other panel. - if (grouping.BeatmapSetsGroupedTogether && (top.Model is BeatmapInfo || bottom.Model is BeatmapInfo)) - return SPACING; + if (grouping.BeatmapSetsGroupedTogether) + { + // Give some space around the expanded beatmap set, at the top.. + if (bottom.Model is BeatmapSetInfo && bottom.IsExpanded) + return SPACING * 2; - if (!grouping.BeatmapSetsGroupedTogether && (top == CurrentSelectionItem || bottom == CurrentSelectionItem)) - return SPACING * 2; + // ..and the bottom. + if (top.Model is BeatmapInfo && bottom.Model is BeatmapSetInfo) + return SPACING * 2; + + // Beatmap difficulty panels do not overlap with themselves or any other panel. + if (top.Model is BeatmapInfo || bottom.Model is BeatmapInfo) + return SPACING; + } + else + { + if (top == CurrentSelectionItem || bottom == CurrentSelectionItem) + return SPACING * 2; + } return -SPACING; } From 6e1a1bb5622722db03776d5da88dca2d4a3c8c79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 16:32:07 +0900 Subject: [PATCH 41/53] Inflate padding to left of difficulty icon to make more visually even --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 2 +- osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index a4a1a1e4cd..728f391a46 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.SelectV2 Icon = difficultyIcon = new ConstrainedIconContainer { Size = new Vector2(9f), - Margin = new MarginPadding { Horizontal = 1.5f }, + Margin = new MarginPadding { Left = 2.5f, Right = 1.5f }, Colour = colourProvider.Background5, }; diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 266666e3b5..680e3828ab 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.SelectV2 Icon = difficultyIcon = new ConstrainedIconContainer { Size = new Vector2(12), - Margin = new MarginPadding { Horizontal = 3f }, + Margin = new MarginPadding { Left = 4f, Right = 3f }, Colour = colourProvider.Background5, }; From 9bf111c66a120d3905f157270c9af97e56aba43c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 16:48:48 +0900 Subject: [PATCH 42/53] Adjust panel content alignment slightly --- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 2 +- osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 728f391a46..e785448c9a 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.SelectV2 { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 2, Bottom = 2 }, + Padding = new MarginPadding { Bottom = 4 }, Children = new Drawable[] { keyCountText = new OsuSpriteText diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 680e3828ab..d461653dcb 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -130,6 +130,7 @@ namespace osu.Game.Screens.SelectV2 Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Direction = FillDirection.Vertical, + Padding = new MarginPadding { Bottom = 2 }, AutoSizeAxes = Axes.Both, Children = new Drawable[] { From 40d801d2c1445451eceea5b6b35b18bbfbf5350b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 16:49:24 +0900 Subject: [PATCH 43/53] Also hide any popovers when footer sets new buttons (ie we're moving to a new screen) --- osu.Game/Screens/Footer/ScreenFooter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index db894e0b49..1baa4ae0ef 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -173,6 +173,7 @@ namespace osu.Game.Screens.Footer temporarilyHiddenButtons.Clear(); overlays.Clear(); + this.HidePopover(); clearActiveOverlayContainer(); var oldButtons = buttonsFlow.ToArray(); From 9d0d3bb97afc093fbced9f99307af13600e79197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 May 2025 10:02:40 +0200 Subject: [PATCH 44/53] Fix test being sensitive to selected tab on new song select --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 046840a691..7aa2ecb06c 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -312,8 +312,8 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("set game volume to max", () => Game.Dependencies.Get().SetValue(FrameworkSetting.VolumeUniversal, 1d)); - AddStep("move to metadata wedge", () => InputManager.MoveMouseTo( - songSelect.ChildrenOfType().Single())); + AddStep("move to details area", () => InputManager.MoveMouseTo( + songSelect.ChildrenOfType().Single())); AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1)); AddAssert("carousel didn't move", getCarouselScrollPosition, () => Is.EqualTo(scrollPosition)); From 27fe9485bd81ec932b6361cb54fdfe9dfeecd66f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 17:01:28 +0900 Subject: [PATCH 45/53] Hide all unsupported grouping modes for now --- osu.Game/Screens/Select/Filter/GroupMode.cs | 16 +++++------ .../SelectV2/BeatmapCarouselFilterGrouping.cs | 28 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs index 3f8ecba86a..b3a4f36c91 100644 --- a/osu.Game/Screens/Select/Filter/GroupMode.cs +++ b/osu.Game/Screens/Select/Filter/GroupMode.cs @@ -19,8 +19,8 @@ namespace osu.Game.Screens.Select.Filter [Description("BPM")] BPM, - [Description("Collections")] - Collections, + // [Description("Collections")] + // Collections, [Description("Date Added")] DateAdded, @@ -31,17 +31,17 @@ namespace osu.Game.Screens.Select.Filter [Description("Difficulty")] Difficulty, - [Description("Favourites")] - Favourites, + // [Description("Favourites")] + // Favourites, [Description("Length")] Length, - [Description("My Maps")] - MyMaps, + // [Description("My Maps")] + // MyMaps, - [Description("Rank Achieved")] - RankAchieved, + // [Description("Rank Achieved")] + // RankAchieved, [Description("Ranked Status")] RankedStatus, diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 78bff0e3c0..8720378ad6 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -199,21 +199,19 @@ namespace osu.Game.Screens.SelectV2 return defineGroupByLength(length); }, items); - case GroupMode.Collections: - // TODO: needs implementation - goto case GroupMode.None; - - case GroupMode.Favourites: - // TODO: needs implementation - goto case GroupMode.None; - - case GroupMode.MyMaps: - // TODO: needs implementation - goto case GroupMode.None; - - case GroupMode.RankAchieved: - // TODO: needs implementation - goto case GroupMode.None; + // TODO: need implementation + // + // case GroupMode.Collections: + // goto case GroupMode.None; + // + // case GroupMode.Favourites: + // goto case GroupMode.None; + // + // case GroupMode.MyMaps: + // goto case GroupMode.None; + // + // case GroupMode.RankAchieved: + // goto case GroupMode.None; default: throw new ArgumentOutOfRangeException(); From 5222c3013976837654653bf24481410ee5c7a1b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 18:07:37 +0900 Subject: [PATCH 46/53] Fix unintentional initial filter delay on entering song select --- osu.Game/Screens/SelectV2/SongSelect.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 59dae1f28a..ebc6adeb93 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -548,8 +548,11 @@ namespace osu.Game.Screens.SelectV2 private void criteriaChanged(FilterCriteria criteria) { + // The first filter needs to be applied immediately as this triggers the initial carousel load. + double filterDelay = filterDebounce == null ? 0 : filter_delay; + filterDebounce?.Cancel(); - filterDebounce = Scheduler.AddDelayed(() => { carousel.Filter(criteria); }, filter_delay); + filterDebounce = Scheduler.AddDelayed(() => { carousel.Filter(criteria); }, filterDelay); } private void newItemsPresented(IEnumerable carouselItems) From 5bf235f2dcb908ccd039577104e1f6a96c056564 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 18:15:27 +0900 Subject: [PATCH 47/53] Use nullable `Criteria` instead of `ctor` flag --- .../SongSelectV2/BeatmapCarouselTestScene.cs | 4 +++- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 18 ++++++------------ osu.Game/Screens/SelectV2/SongSelect.cs | 5 ++++- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index da339bbc3e..d8f173aed2 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -149,6 +149,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 TextAnchor = Anchor.CentreLeft, }, }; + + Carousel.Filter(new FilterCriteria()); }); // Prefer title sorting so that order of carousel panels match order of BeatmapSets bindable. @@ -171,7 +173,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { AddStep(description, () => { - var criteria = Carousel.Criteria; + var criteria = Carousel.Criteria ?? new FilterCriteria(); apply?.Invoke(criteria); Carousel.Filter(criteria); }); diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 32e14ad43a..d3c5fbadf7 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -50,8 +50,6 @@ namespace osu.Game.Screens.SelectV2 private readonly BeatmapCarouselFilterMatching matching; private readonly BeatmapCarouselFilterGrouping grouping; - private bool waitingForInitialCriteria; - /// /// Total number of beatmap difficulties displayed with the filter. /// @@ -70,18 +68,16 @@ namespace osu.Game.Screens.SelectV2 return -SPACING; } - public BeatmapCarousel(bool waitForInitialCriteria = false) + public BeatmapCarousel() { - waitingForInitialCriteria = waitForInitialCriteria; - DebounceDelay = 100; DistanceOffscreenToPreload = 100; Filters = new ICarouselFilter[] { - matching = new BeatmapCarouselFilterMatching(() => Criteria), - new BeatmapCarouselFilterSorting(() => Criteria), - grouping = new BeatmapCarouselFilterGrouping(() => Criteria), + matching = new BeatmapCarouselFilterMatching(() => Criteria!), + new BeatmapCarouselFilterSorting(() => Criteria!), + grouping = new BeatmapCarouselFilterGrouping(() => Criteria!), }; AddInternal(loading = new LoadingLayer()); @@ -472,14 +468,12 @@ namespace osu.Game.Screens.SelectV2 #region Filtering - public FilterCriteria Criteria { get; private set; } = new FilterCriteria(); + public FilterCriteria? Criteria { get; private set; } private ScheduledDelegate? loadingDebounce; public void Filter(FilterCriteria criteria) { - waitingForInitialCriteria = false; - bool resetDisplay = grouping.BeatmapSetsGroupedTogether != BeatmapCarouselFilterGrouping.ShouldGroupBeatmapsTogether(criteria); Criteria = criteria; @@ -502,7 +496,7 @@ namespace osu.Game.Screens.SelectV2 protected override Task> FilterAsync(bool clearExistingPanels = false) { - if (waitingForInitialCriteria) + if (Criteria == null) return Task.FromResult(Enumerable.Empty()); return base.FilterAsync(clearExistingPanels); diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index ebc6adeb93..279cf9e3b1 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -208,7 +208,7 @@ namespace osu.Game.Screens.SelectV2 }, Children = new Drawable[] { - carousel = new BeatmapCarousel(waitForInitialCriteria: true) + carousel = new BeatmapCarousel { BleedTop = FilterControl.HEIGHT_FROM_SCREEN_TOP + 5, BleedBottom = ScreenFooter.HEIGHT + 5, @@ -557,6 +557,9 @@ namespace osu.Game.Screens.SelectV2 private void newItemsPresented(IEnumerable carouselItems) { + if (carousel.Criteria == null) + return; + int count = carousel.MatchedBeatmapsCount; if (count == 0) From 68504e96391ae8cb7dcd04c81cd8ffcfd4976717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 May 2025 11:06:44 +0200 Subject: [PATCH 48/53] Add failing test case --- .../Visual/SongSelectV2/TestSceneBeatmapCarousel.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs index 56351eed97..616748a9d5 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; +using osu.Game.Screens.SelectV2; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.SongSelectV2 @@ -116,5 +118,16 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddStep("add all beatmaps", () => BeatmapSets.AddRange(generated)); } + + [Test] + public void TestSingleItemDisplayed() + { + CreateCarousel(); + RemoveAllBeatmaps(); + + SortAndGroupBy(SortMode.Difficulty, GroupMode.NoGrouping); + AddBeatmaps(1, fixedDifficultiesPerSet: 1); + AddUntilStep("single item is shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); + } } } From 2d900c55cfbd3ac99f5c21ce2b0b509ed6e3e20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 May 2025 11:15:15 +0200 Subject: [PATCH 49/53] SongSelectV2: Fix carousel not displaying anything if there is only one panel to display If there is only one panel to display, a `DisplayRange` of (0, 0) is *very much* valid. --- osu.Game/Graphics/Carousel/Carousel.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 9da4d0e187..9646c86dd6 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -743,6 +743,9 @@ namespace osu.Game.Graphics.Carousel { Debug.Assert(carouselItems != null); + if (carouselItems.Count == 0) + return DisplayRange.EMPTY; + // Find index range of all items that should be on-screen carouselBoundsItem.CarouselYPosition = visibleUpperBound - DistanceOffscreenToPreload; int firstIndex = carouselItems.BinarySearch(carouselBoundsItem); @@ -762,7 +765,7 @@ namespace osu.Game.Graphics.Carousel { Debug.Assert(carouselItems != null); - List toDisplay = range.Last - range.First == 0 + List toDisplay = range == DisplayRange.EMPTY ? new List() : carouselItems.GetRange(range.First, range.Last - range.First + 1); @@ -881,7 +884,10 @@ namespace osu.Game.Graphics.Carousel /// The index of the selection as of the last run of . May be null if selection is not present as an item, or if has not been run yet. private record Selection(object? Model = null, CarouselItem? CarouselItem = null, double? YPosition = null, int? Index = null); - private record DisplayRange(int First, int Last); + private record DisplayRange(int First, int Last) + { + public static readonly DisplayRange EMPTY = new DisplayRange(-1, -1); + } /// /// Implementation of scroll container which handles very large vertical lists by internally using double precision From 671c08df8eedc6ab7e179dc13a3cc9c5aa740a15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 17:51:39 +0900 Subject: [PATCH 50/53] Add test showing carousel never loading under high beatmap churn --- .../SongSelectV2/TestSceneBeatmapCarousel.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs index d9bb612a32..ce671c7e7f 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarousel.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Screens.Select.Filter; @@ -94,6 +95,25 @@ namespace osu.Game.Tests.Visual.SongSelectV2 }); } + [Test] + public void TestHighChurnUpdatesStillShowsPanels() + { + ScheduledDelegate updateTask = null!; + + AddBeatmaps(1, 1); + + AddStep("start constantly updating beatmap in background", () => + { + updateTask = Scheduler.AddDelayed(() => { BeatmapSets.ReplaceRange(0, 1, [BeatmapSets.First()]); }, 1, true); + }); + + CreateCarousel(); + + AddUntilStep("panels loaded", () => Carousel.ChildrenOfType(), () => Is.Not.Empty); + + AddStep("end task", () => updateTask.Cancel()); + } + [Test] [Explicit] public void TestPerformanceWithManyBeatmaps() From 5cf173196ebd65bbf7991057d0df8e54c04327e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 17:37:09 +0900 Subject: [PATCH 51/53] Fix incorrect cross-thread access of beatmap list --- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index e9a8148d5a..b85b7cba45 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -103,20 +103,20 @@ namespace osu.Game.Screens.SelectV2 private void load(BeatmapStore beatmapStore, AudioManager audio, OsuConfigManager config, CancellationToken? cancellationToken) { setupPools(); - setupBeatmaps(beatmapStore, cancellationToken); + detachedBeatmaps = beatmapStore.GetBeatmapSets(cancellationToken); loadSamples(audio); config.BindWith(OsuSetting.RandomSelectAlgorithm, randomAlgorithm); } - #region Beatmap source hookup - - private void setupBeatmaps(BeatmapStore beatmapStore, CancellationToken? cancellationToken) + protected override void LoadComplete() { - detachedBeatmaps = beatmapStore.GetBeatmapSets(cancellationToken); + base.LoadComplete(); detachedBeatmaps.BindCollectionChanged(beatmapSetsChanged, true); } + #region Beatmap source hookup + private void beatmapSetsChanged(object? beatmaps, NotifyCollectionChangedEventArgs changed) { // TODO: moving management of BeatmapInfo tracking to BeatmapStore might be something we want to consider. From 7025c2cf2ff471bb36f70537b06e939a51c5f192 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 17:48:06 +0900 Subject: [PATCH 52/53] Fix missing `ConfigureAwait` causing `Items` to potentially be copied on non-update thread --- .../Visual/SongSelectV2/BeatmapCarouselTestScene.cs | 4 ++-- osu.Game/Graphics/Carousel/Carousel.cs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index 1920647e38..f58d879141 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -409,10 +409,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 protected override async Task> FilterAsync(bool clearExistingPanels = false) { - var items = await base.FilterAsync(clearExistingPanels); + var items = await base.FilterAsync(clearExistingPanels).ConfigureAwait(true); if (FilterDelay != 0) - await Task.Delay(FilterDelay); + await Task.Delay(FilterDelay).ConfigureAwait(true); PostFilterBeatmaps = items.Select(i => i.Model).OfType(); return items; diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 85419a8009..81b656daa1 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -12,6 +12,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Development; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -309,16 +310,17 @@ namespace osu.Game.Graphics.Carousel var cts = new CancellationTokenSource(); var previousCancellationSource = Interlocked.Exchange(ref cancellationSource, cts); - await previousCancellationSource.CancelAsync().ConfigureAwait(false); + await previousCancellationSource.CancelAsync().ConfigureAwait(true); if (DebounceDelay > 0) { log($"Filter operation queued, waiting for {DebounceDelay} ms debounce"); - await Task.Delay(DebounceDelay, cts.Token).ConfigureAwait(false); + await Task.Delay(DebounceDelay, cts.Token).ConfigureAwait(true); } // Copy must be performed on update thread for now (see ConfigureAwait above). // Could potentially be optimised in the future if it becomes an issue. + Debug.Assert(ThreadSafety.IsUpdateThread); List items = new List(Items.Select(m => new CarouselItem(m))); await Task.Run(async () => From 5e397d9d1fd339946890d58f21f6b6e942181450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 17:37:24 +0900 Subject: [PATCH 53/53] Fix carousel never displaying if too many beatmap updates are arriving in background --- osu.Game/Graphics/Carousel/Carousel.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 81b656daa1..f59891c667 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -198,6 +198,8 @@ namespace osu.Game.Graphics.Carousel if (clearExistingPanels) filterReusesPanels.Invalidate(); + filterAfterItemsChanged.Validate(); + filterTask = performFilter(); filterTask.FireAndForget(); return filterTask; @@ -281,7 +283,7 @@ namespace osu.Game.Graphics.Carousel RelativeSizeAxes = Axes.Both, }; - Items.BindCollectionChanged((_, _) => FilterAsync()); + Items.BindCollectionChanged((_, _) => filterAfterItemsChanged.Invalidate()); } [BackgroundDependencyLoader] @@ -304,6 +306,12 @@ namespace osu.Game.Graphics.Carousel private Task> filterTask = Task.FromResult(Enumerable.Empty()); private CancellationTokenSource cancellationSource = new CancellationTokenSource(); + /// + /// For background re-filters, ensure we wait for the previous filter operation to complete before starting another. + /// This avoids the carousel never updating its display in high churn scenarios. + /// + private readonly Cached filterAfterItemsChanged = new Cached(); + private async Task> performFilter() { Stopwatch stopwatch = Stopwatch.StartNew(); @@ -726,6 +734,9 @@ namespace osu.Game.Graphics.Carousel c.KeyboardSelected.Value = c.Item == currentKeyboardSelection?.CarouselItem; c.Expanded.Value = c.Item.IsExpanded; } + + if (!filterAfterItemsChanged.IsValid && !IsFiltering) + FilterAsync(); } protected virtual float GetPanelXOffset(Drawable panel)