From 40303832761831e74ac31175205151257bf9030c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Aug 2025 14:45:20 +0200 Subject: [PATCH 01/11] Allow grouping modes that apply max aggregate to split beatmap sets apart --- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index f17281db2f..f0ec3ae3ab 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -43,7 +43,8 @@ namespace osu.Game.Screens.SelectV2 private readonly Func> getCollections; private readonly Func> getLocalUserTopRanks; - public BeatmapCarouselFilterGrouping(Func getCriteria, Func> getCollections, Func> getLocalUserTopRanks) + public BeatmapCarouselFilterGrouping(Func getCriteria, Func> getCollections, + Func> getLocalUserTopRanks) { this.getCriteria = getCriteria; this.getCollections = getCollections; @@ -189,9 +190,6 @@ namespace osu.Game.Screens.SelectV2 { var date = b.LastPlayed; - if (BeatmapSetsGroupedTogether) - date = aggregateMax(b, static b => b.LastPlayed ?? DateTimeOffset.MinValue); - if (date == null || date == DateTimeOffset.MinValue) return new GroupDefinition(int.MaxValue, "Never"); @@ -202,29 +200,13 @@ namespace osu.Game.Screens.SelectV2 return getGroupsBy(b => defineGroupByStatus(b.BeatmapSet!.Status), items); case GroupMode.BPM: - return getGroupsBy(b => - { - double bpm = FormatUtils.RoundBPM(b.BPM); - - if (BeatmapSetsGroupedTogether) - bpm = aggregateMax(b, bb => FormatUtils.RoundBPM(bb.BPM)); - - return defineGroupByBPM(bpm); - }, items); + return getGroupsBy(b => defineGroupByBPM(FormatUtils.RoundBPM(b.BPM)), items); case GroupMode.Difficulty: return getGroupsBy(b => defineGroupByStars(b.StarRating), items); case GroupMode.Length: - return getGroupsBy(b => - { - double length = b.Length; - - if (BeatmapSetsGroupedTogether) - length = aggregateMax(b, bb => bb.Length); - - return defineGroupByLength(length); - }, items); + return getGroupsBy(b => defineGroupByLength(b.Length), items); case GroupMode.Source: return getGroupsBy(b => defineGroupBySource(b.BeatmapSet!.Metadata.Source), items); @@ -433,12 +415,6 @@ namespace osu.Game.Screens.SelectV2 return new GroupDefinition(int.MaxValue, "Unplayed"); } - private static T? aggregateMax(BeatmapInfo b, Func func) - { - var beatmaps = b.BeatmapSet!.Beatmaps.Where(bb => !bb.Hidden); - return beatmaps.Max(func); - } - private record GroupMapping(GroupDefinition? Group, List ItemsInGroup); } } From 311c75aa533979521981aa5ed70494d3c57a4f4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Aug 2025 10:26:31 +0200 Subject: [PATCH 02/11] Adjust test after allowing grouping modes to split beatmap sets apart --- .../BeatmapCarouselFilterGroupingTest.cs | 107 +++++++++--------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs index c8f1c1e017..592994f2f0 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs @@ -74,11 +74,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(applyBeatmap('_'), beatmapSets, out var underscoreBeatmap); var results = await runGrouping(mode, beatmapSets); - assertGroup(results, 0, "0-9", new[] { fiveBeatmap, fourBeatmap }, ref total); - assertGroup(results, 1, "A", new[] { aBeatmap }, ref total); - assertGroup(results, 2, "F", new[] { fBeatmap }, ref total); - assertGroup(results, 3, "Z", new[] { zBeatmap }, ref total); - assertGroup(results, 4, "Other", new[] { dashBeatmap, underscoreBeatmap }, ref total); + assertGroup(results, 0, "0-9", fiveBeatmap.Beatmaps.Concat(fourBeatmap.Beatmaps), ref total); + assertGroup(results, 1, "A", aBeatmap.Beatmaps, ref total); + assertGroup(results, 2, "F", fBeatmap.Beatmaps, ref total); + assertGroup(results, 3, "Z", zBeatmap.Beatmaps, ref total); + assertGroup(results, 4, "Other", dashBeatmap.Beatmaps.Concat(underscoreBeatmap.Beatmaps), ref total); assertTotal(results, total); } @@ -115,12 +115,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddMonths(-2).AddDays(-3), beatmapSets, out var twoMonthsAgoBeatmap); var results = await runGrouping(GroupMode.DateAdded, beatmapSets); - assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total); - assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total); - assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total); - assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total); - assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total); - assertGroup(results, 5, "2 months ago", new[] { twoMonthsAgoBeatmap }, ref total); + assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total); + assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total); + assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total); + assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total); + assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total); + assertGroup(results, 5, "2 months ago", twoMonthsAgoBeatmap.Beatmaps, ref total); assertTotal(results, total); } @@ -139,13 +139,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap); var results = await runGrouping(GroupMode.LastPlayed, beatmapSets); - assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total); - assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total); - assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total); - assertGroup(results, 3, "Last month", new[] { lastMonthBeatmap }, ref total); - assertGroup(results, 4, "1 month ago", new[] { oneMonthAgoBeatmap }, ref total); - assertGroup(results, 5, "2 months ago", new[] { twoMonthsBeatmap }, ref total); - assertGroup(results, 6, "Never", new[] { neverBeatmap }, ref total); + assertGroup(results, 0, "Today", todayBeatmap.Beatmaps, ref total); + assertGroup(results, 1, "Yesterday", yesterdayBeatmap.Beatmaps, ref total); + assertGroup(results, 2, "Last week", lastWeekBeatmap.Beatmaps, ref total); + assertGroup(results, 3, "Last month", lastMonthBeatmap.Beatmaps, ref total); + assertGroup(results, 4, "1 month ago", oneMonthAgoBeatmap.Beatmaps, ref total); + assertGroup(results, 5, "2 months ago", twoMonthsBeatmap.Beatmaps, ref total); + assertGroup(results, 6, "Never", neverBeatmap.Beatmaps, ref total); assertTotal(results, total); } @@ -162,7 +162,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 var results = await runGrouping(GroupMode.LastPlayed, beatmapSets); int total = 0; - assertGroup(results, 0, "Today", new[] { set }, ref total); + assertGroup(results, 0, "Today", [set.Beatmaps[2]], ref total); + assertGroup(results, 1, "Never", [set.Beatmaps[0], set.Beatmaps[1]], ref total); assertTotal(results, total); } @@ -176,8 +177,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 var results = await runGrouping(GroupMode.LastPlayed, beatmapSets); int total = 0; - assertGroup(results, 0, "Over 5 months ago", new[] { overFiveMonthsBeatmap }, ref total); - assertGroup(results, 1, "Never", new[] { neverBeatmap }, ref total); + assertGroup(results, 0, "Over 5 months ago", overFiveMonthsBeatmap.Beatmaps, ref total); + assertGroup(results, 1, "Never", neverBeatmap.Beatmaps, ref total); assertTotal(results, total); } @@ -207,14 +208,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(s => s.Status = BeatmapOnlineStatus.LocallyModified, beatmapSets, out var localBeatmap); var results = await runGrouping(GroupMode.RankedStatus, beatmapSets); - assertGroup(results, 0, "Ranked", new[] { rankedBeatmap, approvedBeatmap }, ref total); - assertGroup(results, 1, "Qualified", new[] { qualifiedBeatmap }, ref total); - assertGroup(results, 2, "WIP", new[] { wipBeatmap }, ref total); - assertGroup(results, 3, "Pending", new[] { pendingBeatmap }, ref total); - assertGroup(results, 4, "Graveyard", new[] { graveyardBeatmap }, ref total); - assertGroup(results, 5, "Local", new[] { localBeatmap }, ref total); - assertGroup(results, 6, "Unknown", new[] { noneBeatmap }, ref total); - assertGroup(results, 7, "Loved", new[] { lovedBeatmap }, ref total); + assertGroup(results, 0, "Ranked", rankedBeatmap.Beatmaps.Concat(approvedBeatmap.Beatmaps), ref total); + assertGroup(results, 1, "Qualified", qualifiedBeatmap.Beatmaps, ref total); + assertGroup(results, 2, "WIP", wipBeatmap.Beatmaps, ref total); + assertGroup(results, 3, "Pending", pendingBeatmap.Beatmaps, ref total); + assertGroup(results, 4, "Graveyard", graveyardBeatmap.Beatmaps, ref total); + assertGroup(results, 5, "Local", localBeatmap.Beatmaps, ref total); + assertGroup(results, 6, "Unknown", noneBeatmap.Beatmaps, ref total); + assertGroup(results, 7, "Loved", lovedBeatmap.Beatmaps, ref total); assertTotal(results, total); } @@ -240,12 +241,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(applyBPM(330), beatmapSets, out var beatmap330); var results = await runGrouping(GroupMode.BPM, beatmapSets); - assertGroup(results, 0, "Under 60 BPM", new[] { beatmap30 }, ref total); - assertGroup(results, 1, "60 - 70 BPM", new[] { beatmap59, beatmap60 }, ref total); - assertGroup(results, 2, "90 - 100 BPM", new[] { beatmap90, beatmap95 }, ref total); - assertGroup(results, 3, "270 - 280 BPM", new[] { beatmap269, beatmap270 }, ref total); - assertGroup(results, 4, "290 - 300 BPM", new[] { beatmap299 }, ref total); - assertGroup(results, 5, "Over 300 BPM", new[] { beatmap300, beatmap330 }, ref total); + assertGroup(results, 0, "Under 60 BPM", beatmap30.Beatmaps, ref total); + assertGroup(results, 1, "60 - 70 BPM", (beatmap59.Beatmaps.Concat(beatmap60.Beatmaps)), ref total); + assertGroup(results, 2, "90 - 100 BPM", (beatmap90.Beatmaps.Concat(beatmap95.Beatmaps)), ref total); + assertGroup(results, 3, "270 - 280 BPM", (beatmap269.Beatmaps.Concat(beatmap270.Beatmaps)), ref total); + assertGroup(results, 4, "290 - 300 BPM", beatmap299.Beatmaps, ref total); + assertGroup(results, 5, "Over 300 BPM", (beatmap300.Beatmaps.Concat(beatmap330.Beatmaps)), ref total); assertTotal(results, total); } @@ -272,10 +273,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7); var results = await runGrouping(GroupMode.Difficulty, beatmapSets); - assertGroup(results, 0, "Below 1 Star", new[] { beatmapBelow1 }, ref total); - assertGroup(results, 1, "1 Star", new[] { beatmapAbove1, beatmapAlmost2 }, ref total); - assertGroup(results, 2, "2 Stars", new[] { beatmap2, beatmapAbove2 }, ref total); - assertGroup(results, 3, "7 Stars", new[] { beatmap7 }, ref total); + assertGroup(results, 0, "Below 1 Star", beatmapBelow1.Beatmaps, ref total); + assertGroup(results, 1, "1 Star", (beatmapAbove1.Beatmaps.Concat(beatmapAlmost2.Beatmaps)), ref total); + assertGroup(results, 2, "2 Stars", (beatmap2.Beatmaps.Concat(beatmapAbove2.Beatmaps)), ref total); + assertGroup(results, 3, "7 Stars", beatmap7.Beatmaps, ref total); assertTotal(results, total); } @@ -304,11 +305,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(applyLength(630_000), beatmapSets, out var beatmap10Min30Sec); var results = await runGrouping(GroupMode.Length, beatmapSets); - assertGroup(results, 0, "1 minute or less", new[] { beatmap30Sec, beatmap1Min }, ref total); - assertGroup(results, 1, "2 minutes or less", new[] { beatmap1Min30Sec, beatmap2Min }, ref total); - assertGroup(results, 2, "5 minutes or less", new[] { beatmap5Min }, ref total); - assertGroup(results, 3, "10 minutes or less", new[] { beatmap6Min, beatmap10Min }, ref total); - assertGroup(results, 4, "Over 10 minutes", new[] { beatmap10Min30Sec }, ref total); + assertGroup(results, 0, "1 minute or less", (beatmap30Sec.Beatmaps.Concat(beatmap1Min.Beatmaps)), ref total); + assertGroup(results, 1, "2 minutes or less", (beatmap1Min30Sec.Beatmaps.Concat(beatmap2Min.Beatmaps)), ref total); + assertGroup(results, 2, "5 minutes or less", beatmap5Min.Beatmaps, ref total); + assertGroup(results, 3, "10 minutes or less", (beatmap6Min.Beatmaps.Concat(beatmap10Min.Beatmaps)), ref total); + assertGroup(results, 4, "Over 10 minutes", beatmap10Min30Sec.Beatmaps, ref total); assertTotal(results, total); } @@ -334,10 +335,10 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(s => s.DateRanked = null, beatmapSets, out var beatmapUnranked); var results = await runGrouping(GroupMode.DateRanked, beatmapSets); - assertGroup(results, 0, "2025", new[] { beatmap2025 }, ref total); - assertGroup(results, 1, "2010", new[] { beatmap2010 }, ref total); - assertGroup(results, 2, "2007", new[] { beatmapOct2007, beatmapDec2007 }, ref total); - assertGroup(results, 3, "Unranked", new[] { beatmapUnranked }, ref total); + assertGroup(results, 0, "2025", beatmap2025.Beatmaps, ref total); + assertGroup(results, 1, "2010", beatmap2010.Beatmaps, ref total); + assertGroup(results, 2, "2007", (beatmapOct2007.Beatmaps.Concat(beatmapDec2007.Beatmaps)), ref total); + assertGroup(results, 3, "Unranked", beatmapUnranked.Beatmaps, ref total); assertTotal(results, total); } @@ -357,9 +358,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced); var results = await runGrouping(GroupMode.Source, beatmapSets); - assertGroup(results, 0, "Cool Game", new[] { beatmapCoolGame, beatmapCoolGameB }, ref total); - assertGroup(results, 1, "Nice Movie", new[] { beatmapNiceMovie }, ref total); - assertGroup(results, 2, "Unsourced", new[] { beatmapUnsourced }, ref total); + assertGroup(results, 0, "Cool Game", (beatmapCoolGame.Beatmaps.Concat(beatmapCoolGameB.Beatmaps)), ref total); + assertGroup(results, 1, "Nice Movie", beatmapNiceMovie.Beatmaps, ref total); + assertGroup(results, 2, "Unsourced", beatmapUnsourced.Beatmaps, ref total); assertTotal(results, total); } @@ -375,7 +376,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None); } - private static void assertGroup(List items, int index, string expectedTitle, IEnumerable expectedBeatmapSets, ref int totalItems) + private static void assertGroup(List items, int index, string expectedTitle, IEnumerable expectedBeatmaps, ref int totalItems) { var groupItem = items.Where(i => i.Model is GroupDefinition).ElementAtOrDefault(index); @@ -390,7 +391,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 var groupModel = (GroupDefinition)groupItem.Model; Assert.That(groupModel.Title, Is.EqualTo(expectedTitle)); - Assert.That(itemsInGroup.Select(i => i.Model).OfType(), Is.EquivalentTo(expectedBeatmapSets.SelectMany(bs => bs.Beatmaps))); + Assert.That(itemsInGroup.Select(i => i.Model).OfType(), Is.EquivalentTo(expectedBeatmaps)); totalItems += itemsInGroup.Count() + 1; } From 47164c61b4889a8e1af7c871ffb7c3b751ed425d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Aug 2025 14:45:32 +0200 Subject: [PATCH 03/11] Add failing test coverage of splitting beatmap sets apart --- .../TestSceneBeatmapCarouselSetsSplitApart.cs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetsSplitApart.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetsSplitApart.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetsSplitApart.cs new file mode 100644 index 0000000000..fa635f9bde --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselSetsSplitApart.cs @@ -0,0 +1,120 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Filter; +using osu.Game.Screens.SelectV2; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + [TestFixture] + public partial class TestSceneBeatmapCarouselSetsSplitApart : BeatmapCarouselTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + RemoveAllBeatmaps(); + CreateCarousel(); + + SortAndGroupBy(SortMode.Title, GroupMode.Length); + } + + [Test] + public void TestSetTraversal() + { + AddBeatmaps(3, splitApart: true); + AddBeatmaps(3, splitApart: false); + WaitForDrawablePanels(); + + SelectNextSet(); + WaitForSetSelection(set: 0, diff: 0); + + SelectNextSet(); + WaitForSetSelection(set: 1, diff: 0); + + SelectPrevSet(); + WaitForSetSelection(set: 0, diff: 0); + + SelectPrevSet(); + WaitForSetSelection(set: 5, diff: 0); + + SelectPrevSet(); + SelectPrevSet(); + SelectPrevSet(); + WaitForSetSelection(set: 2, diff: 4); + AddAssert("only two beatmap panels visible", () => GetVisiblePanels().Count(), () => Is.EqualTo(2)); + } + + [Test] + public void TestBeatmapTraversal() + { + AddBeatmaps(3, splitApart: true); + AddBeatmaps(3, splitApart: false); + WaitForDrawablePanels(); + + SelectNextSet(); + WaitForSetSelection(set: 0, diff: 0); + + SelectNextPanel(); + WaitForSetSelection(set: 0, diff: 1); + + SelectNextPanel(); // header of set 1 in group 0 + Select(); + WaitForSetSelection(set: 1, diff: 0); + + SelectPrevPanel(); // header of set 1 in group 0 + SelectPrevPanel(); // header of set 0 in group 0 + Select(); + WaitForSetSelection(set: 0, diff: 0); + + SelectPrevPanel(); // header of set 0 in group 0 + SelectPrevPanel(); // header of group 0 + SelectPrevPanel(); // header of group 2 + Select(); + SelectNextPanel(); // header of set 0 in group 2 + Select(); + WaitForSetSelection(set: 0, diff: 4); + } + + [Test] + public void TestRandomStaysInGroup() + { + AddBeatmaps(2, splitApart: false); + AddBeatmaps(1, splitApart: true); + WaitForDrawablePanels(); + + SelectPrevSet(); + SelectPrevSet(); + WaitForSetSelection(set: 1); + WaitForExpandedGroup(2); + + AddStep("select next random", () => Carousel.NextRandom()); + WaitForExpandedGroup(2); + AddStep("select next random", () => Carousel.NextRandom()); + WaitForExpandedGroup(2); + } + + protected void AddBeatmaps(int count, bool splitApart) => AddStep($"add {count} beatmaps ({(splitApart ? "" : "not ")}split apart)", () => + { + var beatmapSets = new List(); + + for (int i = 0; i < count; i++) + { + var beatmapSet = CreateTestBeatmapSetInfo(6, false); + + for (int j = 0; j < beatmapSet.Beatmaps.Count; j++) + { + beatmapSet.Beatmaps[j].Length = splitApart ? 30_000 * (j + 1) : 180_000; + } + + beatmapSets.Add(beatmapSet); + } + + BeatmapSets.AddRange(beatmapSets); + }); + } +} From 8dd131f17ee57e8cbe005b8a8abb8e0ff3a0c4aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Aug 2025 09:40:22 +0200 Subject: [PATCH 04/11] Support beatmap sets being split apart by the active group mode in beatmap carousel --- .../SongSelectV2/BeatmapCarouselTestScene.cs | 2 +- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 70 +++++++++++-------- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 15 ++-- osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 19 +++-- 4 files changed, 60 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index bc507fbffa..64084d76f1 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo; public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet; - public new BeatmapSetInfo? ExpandedBeatmapSet => base.ExpandedBeatmapSet; + public new BeatmapSetUnderGrouping? ExpandedBeatmapSet => base.ExpandedBeatmapSet; public new GroupDefinition? ExpandedGroup => base.ExpandedGroup; public TestBeatmapCarousel() diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index da841aa361..95fb26c6dd 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -69,11 +70,11 @@ namespace osu.Game.Screens.SelectV2 if (grouping.BeatmapSetsGroupedTogether) { // Give some space around the expanded beatmap set, at the top.. - if (bottom.Model is BeatmapSetInfo && bottom.IsExpanded) + if (bottom.Model is BeatmapSetUnderGrouping && bottom.IsExpanded) return SPACING * 2; // ..and the bottom. - if (top.Model is BeatmapInfo && bottom.Model is BeatmapSetInfo) + if (top.Model is BeatmapInfo && bottom.Model is BeatmapSetUnderGrouping) return SPACING * 2; // Beatmap difficulty panels do not overlap with themselves or any other panel. @@ -206,12 +207,12 @@ namespace osu.Game.Screens.SelectV2 return true; } - if (item.Model is BeatmapSetInfo beatmapSetInfo) + if (item.Model is BeatmapSetUnderGrouping setUnderGrouping) { - if (oldItems.Contains(beatmapSetInfo)) + if (oldItems.Contains(setUnderGrouping.BeatmapSet)) return false; - RequestRecommendedSelection(beatmapSetInfo.Beatmaps); + RequestRecommendedSelection(setUnderGrouping.BeatmapSet.Beatmaps); return true; } } @@ -282,7 +283,7 @@ namespace osu.Game.Screens.SelectV2 protected GroupDefinition? ExpandedGroup { get; private set; } - protected BeatmapSetInfo? ExpandedBeatmapSet { get; private set; } + protected BeatmapSetUnderGrouping? ExpandedBeatmapSet { get; private set; } protected override bool ShouldActivateOnKeyboardSelection(CarouselItem item) => grouping.BeatmapSetsGroupedTogether && item.Model is BeatmapInfo; @@ -310,8 +311,8 @@ namespace osu.Game.Screens.SelectV2 return; - case BeatmapSetInfo setInfo: - selectRecommendedDifficultyForBeatmapSet(setInfo); + case BeatmapSetUnderGrouping setUnderGrouping: + selectRecommendedDifficultyForBeatmapSet(setUnderGrouping); return; case BeatmapInfo beatmapInfo: @@ -337,7 +338,7 @@ namespace osu.Game.Screens.SelectV2 switch (model) { - case BeatmapSetInfo: + case BeatmapSetUnderGrouping: case GroupDefinition: throw new InvalidOperationException("Groups should never become selected"); @@ -348,7 +349,7 @@ namespace osu.Game.Screens.SelectV2 setExpandedGroup(containingGroup); if (grouping.BeatmapSetsGroupedTogether) - setExpandedSet(beatmapInfo); + setExpandedSet(new BeatmapSetUnderGrouping(containingGroup, beatmapInfo.BeatmapSet!)); break; } } @@ -372,10 +373,10 @@ namespace osu.Game.Screens.SelectV2 setExpandedGroup(groupForReselection); } - private void selectRecommendedDifficultyForBeatmapSet(BeatmapSetInfo beatmapSet) + private void selectRecommendedDifficultyForBeatmapSet(BeatmapSetUnderGrouping setUnderGrouping) { // Selecting a set isn't valid – let's re-select the first visible difficulty. - if (grouping.SetItems.TryGetValue(beatmapSet, out var items)) + if (grouping.SetItems.TryGetValue(setUnderGrouping, out var items)) { var beatmaps = items.Select(i => i.Model).OfType(); RequestRecommendedSelection(beatmaps); @@ -423,7 +424,7 @@ namespace osu.Game.Screens.SelectV2 { switch (item.Model) { - case BeatmapSetInfo: + case BeatmapSetUnderGrouping: return true; case BeatmapInfo: @@ -462,11 +463,11 @@ namespace osu.Game.Screens.SelectV2 i.IsExpanded = true; break; - case BeatmapSetInfo set: + case BeatmapSetUnderGrouping setUnderGrouping: // Case where there are set headers, header should be visible // and items should use the set's expanded state. i.IsVisible = true; - setExpansionStateOfSetItems(set, i.IsExpanded); + setExpansionStateOfSetItems(setUnderGrouping, i.IsExpanded); break; default: @@ -496,21 +497,21 @@ namespace osu.Game.Screens.SelectV2 } } - private void setExpandedSet(BeatmapInfo beatmapInfo) + private void setExpandedSet(BeatmapSetUnderGrouping setUnderGrouping) { if (ExpandedBeatmapSet != null) setExpansionStateOfSetItems(ExpandedBeatmapSet, false); - ExpandedBeatmapSet = beatmapInfo.BeatmapSet!; + ExpandedBeatmapSet = setUnderGrouping; setExpansionStateOfSetItems(ExpandedBeatmapSet, true); } - private void setExpansionStateOfSetItems(BeatmapSetInfo set, bool expanded) + private void setExpansionStateOfSetItems(BeatmapSetUnderGrouping set, bool expanded) { if (grouping.SetItems.TryGetValue(set, out var items)) { foreach (var i in items) { - if (i.Model is BeatmapSetInfo) + if (i.Model is BeatmapSetUnderGrouping) i.IsExpanded = expanded; else i.IsVisible = expanded; @@ -548,7 +549,7 @@ namespace osu.Game.Screens.SelectV2 sampleToggleGroup?.Play(); return; - case BeatmapSetInfo: + case BeatmapSetUnderGrouping: sampleChangeSet?.Play(); return; @@ -687,8 +688,8 @@ namespace osu.Game.Screens.SelectV2 // it is doing a Replace operation on the list. If it is, then check the local handling in beatmapSetsChanged // before changing matching requirements here. - if (x is BeatmapSetInfo beatmapSetX && y is BeatmapSetInfo beatmapSetY) - return beatmapSetX.Equals(beatmapSetY); + if (x is BeatmapSetUnderGrouping setUnderGroupingX && y is BeatmapSetUnderGrouping setUnderGroupingY) + return setUnderGroupingX.Equals(setUnderGroupingY); if (x is BeatmapInfo beatmapX && y is BeatmapInfo beatmapY) return beatmapX.Equals(beatmapY); @@ -718,7 +719,7 @@ namespace osu.Game.Screens.SelectV2 return beatmapPanelPool.Get(); - case BeatmapSetInfo: + case BeatmapSetUnderGrouping: return setPanelPool.Get(); } @@ -828,30 +829,31 @@ namespace osu.Game.Screens.SelectV2 private bool nextRandomSet() { - ICollection visibleSets = ExpandedGroup != null + ICollection visibleSetsUnderGrouping = ExpandedGroup != null // In the case of grouping, users expect random to only operate on the expanded group. // This is going to incur some overhead as we don't have a group-beatmapset mapping currently. // // If this becomes an issue, we could either store a mapping, or run the random algorithm many times // using the `SetItems` method until we get a group HIT. - ? grouping.GroupItems[ExpandedGroup].Select(i => i.Model).OfType().ToArray() + ? grouping.GroupItems[ExpandedGroup].Select(i => i.Model).OfType().ToArray() // This is the fastest way to retrieve sets for randomisation. : grouping.SetItems.Keys; - BeatmapSetInfo set; + BeatmapSetUnderGrouping set; switch (randomAlgorithm.Value) { case RandomSelectAlgorithm.RandomPermutation: { - ICollection notYetVisitedSets = visibleSets.Except(previouslyVisitedRandomBeatmaps.Select(b => b.BeatmapSet!)).ToList(); + ICollection notYetVisitedSets = + visibleSetsUnderGrouping.ExceptBy(previouslyVisitedRandomBeatmaps.Select(b => b.BeatmapSet!), setUnderGrouping => setUnderGrouping.BeatmapSet).ToList(); if (!notYetVisitedSets.Any()) { - previouslyVisitedRandomBeatmaps.RemoveAll(b => visibleSets.Contains(b.BeatmapSet!)); - notYetVisitedSets = visibleSets; + previouslyVisitedRandomBeatmaps.RemoveAll(b => visibleSetsUnderGrouping.Any(setUnderGrouping => setUnderGrouping.BeatmapSet.Equals(b.BeatmapSet!))); + notYetVisitedSets = visibleSetsUnderGrouping; if (CurrentSelection is BeatmapInfo beatmapInfo) - notYetVisitedSets = notYetVisitedSets.Except([beatmapInfo.BeatmapSet!]).ToList(); + notYetVisitedSets = notYetVisitedSets.ExceptBy([beatmapInfo.BeatmapSet!], setUnderGrouping => setUnderGrouping.BeatmapSet).ToList(); } if (notYetVisitedSets.Count == 0) @@ -862,7 +864,7 @@ namespace osu.Game.Screens.SelectV2 } case RandomSelectAlgorithm.Random: - set = visibleSets.ElementAt(RNG.Next(visibleSets.Count)); + set = visibleSetsUnderGrouping.ElementAt(RNG.Next(visibleSetsUnderGrouping.Count)); break; default: @@ -959,4 +961,10 @@ namespace osu.Game.Screens.SelectV2 /// Defines a grouping header for a set of carousel items grouped by star difficulty. /// public record StarDifficultyGroupDefinition(int Order, string Title, StarDifficulty Difficulty) : GroupDefinition(Order, Title); + + /// + /// Used to represent a portion of a under a . + /// The purpose of this model is to support splitting beatmap sets apart when the active grouping mode demands it. + /// + public record BeatmapSetUnderGrouping([UsedImplicitly] GroupDefinition? Group, BeatmapSetInfo BeatmapSet); } diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index f0ec3ae3ab..63bc94b087 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -29,14 +29,14 @@ namespace osu.Game.Screens.SelectV2 /// /// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection. /// - public IDictionary> SetItems => setMap; + public IDictionary> SetItems => setMap; /// /// Groups contain children which are group-selectable. This dictionary holds the relationships between groups-panels to allow expanding them on selection. /// public IDictionary> GroupItems => groupMap; - private Dictionary> setMap = new Dictionary>(); + private Dictionary> setMap = new Dictionary>(); private Dictionary> groupMap = new Dictionary>(); private readonly Func getCriteria; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.SelectV2 return await Task.Run(() => { // preallocate space for the new mappings using last known estimates - var newSetMap = new Dictionary>(setMap.Count); + var newSetMap = new Dictionary>(setMap.Count); var newGroupMap = new Dictionary>(groupMap.Count); var criteria = getCriteria(); @@ -94,11 +94,12 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)item.Model; bool newBeatmapSet = lastBeatmap?.BeatmapSet!.ID != beatmap.BeatmapSet!.ID; + var beatmapSetUnderGrouping = new BeatmapSetUnderGrouping(group, beatmap.BeatmapSet!); if (newBeatmapSet) { - if (!newSetMap.TryGetValue(beatmap.BeatmapSet!, out currentSetItems)) - newSetMap[beatmap.BeatmapSet!] = currentSetItems = new HashSet(); + if (!newSetMap.TryGetValue(beatmapSetUnderGrouping, out currentSetItems)) + newSetMap[beatmapSetUnderGrouping] = currentSetItems = new HashSet(); } if (BeatmapSetsGroupedTogether) @@ -108,7 +109,7 @@ namespace osu.Game.Screens.SelectV2 if (groupItem != null) groupItem.NestedItemCount++; - addItem(new CarouselItem(beatmap.BeatmapSet!) + addItem(new CarouselItem(beatmapSetUnderGrouping) { DrawHeight = PanelBeatmapSet.HEIGHT, DepthLayer = -1 @@ -135,7 +136,7 @@ namespace osu.Game.Screens.SelectV2 currentGroupItems?.Add(i); currentSetItems?.Add(i); - i.IsVisible = i.Model is GroupDefinition || (group == null && (i.Model is BeatmapSetInfo || !BeatmapSetsGroupedTogether)); + i.IsVisible = i.Model is GroupDefinition || (group == null && (i.Model is BeatmapSetUnderGrouping || !BeatmapSetsGroupedTogether)); } } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index d776ab1ffb..7b07076975 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -67,6 +67,15 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private IBindable ruleset { get; set; } = null!; + private BeatmapSetUnderGrouping beatmapSetUnderGrouping + { + get + { + Debug.Assert(Item != null); + return (BeatmapSetUnderGrouping)Item!.Model; + } + } + public PanelBeatmapSet() { PanelXOffset = 20f; @@ -179,9 +188,7 @@ namespace osu.Game.Screens.SelectV2 { base.PrepareForUse(); - Debug.Assert(Item != null); - - var beatmapSet = (BeatmapSetInfo)Item.Model; + var beatmapSet = beatmapSetUnderGrouping.BeatmapSet; // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). setBackground.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID)); @@ -215,7 +222,7 @@ namespace osu.Game.Screens.SelectV2 if (Item == null) return Array.Empty(); - var beatmapSet = (BeatmapSetInfo)Item.Model; + var beatmapSet = beatmapSetUnderGrouping.BeatmapSet; List items = new List(); @@ -268,9 +275,7 @@ namespace osu.Game.Screens.SelectV2 private MenuItem createCollectionMenuItem(BeatmapCollection collection) { - var beatmapSet = (BeatmapSetInfo)Item!.Model; - - Debug.Assert(beatmapSet != null); + var beatmapSet = beatmapSetUnderGrouping.BeatmapSet; TernaryState state; From 6ba72fa481462932cf770781a2ac28263fd3e4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Aug 2025 10:35:31 +0200 Subject: [PATCH 05/11] Adjust tests to new beatmap set model usage in carousel --- .../Visual/Navigation/TestScenePresentBeatmap.cs | 2 +- .../BeatmapCarouselFilterGroupingTest.cs | 2 +- .../SongSelectV2/BeatmapCarouselTestScene.cs | 2 +- .../TestSceneBeatmapCarouselFiltering.cs | 16 ++++++++++++---- .../Visual/SongSelectV2/TestScenePanelSet.cs | 8 ++++---- .../SongSelectV2/TestSceneSongSelectGrouping.cs | 2 +- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index e7172cacbf..6092bdde3a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("beatmap in song select", () => { var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen; - return songSelect.ChildrenOfType().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetInfo bsi && bsi.MatchesOnlineID(getImport())); + return songSelect.ChildrenOfType().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetUnderGrouping bsug && bsug.BeatmapSet.MatchesOnlineID(getImport())); }); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs index 592994f2f0..efd4eb7b03 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ]; 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().Select(setUnderGrouping => setUnderGrouping.BeatmapSet), 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/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index 64084d76f1..2664062fc2 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 // Using groupingFilter.SetItems.Count alone doesn't work. // When sorting by difficulty, there can be more than one set panel for the same set displayed. - return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetInfo)); + return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetUnderGrouping)); }, () => Is.EqualTo(expected)); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs index 78c12e2730..d599c07f27 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs @@ -396,7 +396,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3); SelectNextPanel(); - AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First())); + AddAssert("keyboard selected is first set", + () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => Is.EqualTo(BeatmapSets.First())); } [Test] @@ -413,7 +415,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ApplyToFilterAndWaitForFilter("filter first away", c => c.UserStarDifficulty.Min = 3); SelectPrevPanel(); - AddAssert("keyboard selected is last set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last())); + AddAssert("keyboard selected is last set", + () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => Is.EqualTo(BeatmapSets.Last())); } [Test] @@ -428,7 +432,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ApplyToFilterAndWaitForFilter("filter last set away", c => c.SearchText = BeatmapSets.First().Metadata.Title); SelectPrevPanel(); - AddAssert("keyboard selected is first set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.First())); + AddAssert("keyboard selected is first set", + () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => Is.EqualTo(BeatmapSets.First())); } [Test] @@ -444,7 +450,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 // Single result is automatically selected for us, so we iterate once backwards to the set header. SelectPrevPanel(); - AddAssert("keyboard selected is second set", () => GetKeyboardSelectedPanel()?.Item?.Model, () => Is.EqualTo(BeatmapSets.Last())); + AddAssert("keyboard selected is second set", + () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => Is.EqualTo(BeatmapSets.Last())); } } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs index 1723185b1f..6a212381a8 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs @@ -75,21 +75,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { new PanelBeatmapSet { - Item = new CarouselItem(beatmapSet) + Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)) }, new PanelBeatmapSet { - Item = new CarouselItem(beatmapSet), + Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)), KeyboardSelected = { Value = true } }, new PanelBeatmapSet { - Item = new CarouselItem(beatmapSet), + Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)), Expanded = { Value = true } }, new PanelBeatmapSet { - Item = new CarouselItem(beatmapSet), + Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)), KeyboardSelected = { Value = true }, Expanded = { Value = true } }, diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs index 0f7c42946d..be7f705532 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("no-collection group present", () => { var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection"); - return group.Value.Select(i => i.Model).OfType().Single().Equals(beatmapSet); + return group.Value.Select(i => i.Model).OfType().Single().BeatmapSet.Equals(beatmapSet); }); AddStep("add beatmap to collection", () => From bb9f9e4d358461d471c682a6af1d83e028523a41 Mon Sep 17 00:00:00 2001 From: marvin Date: Thu, 28 Aug 2025 23:34:22 +0200 Subject: [PATCH 06/11] Fix operations in PooledDrawableWithLifetimeContainer.CheckChildrenLife being in wrong order Previously CompositeDrawable.CheckChildrenLife() would be run before lifetimeManager.Update() which lead to the new drawables being inserted into the container but not being made alive immediately, leading to the drawable not becoming visibile until the next update loop. --- .../Objects/Pooling/PooledDrawableWithLifetimeContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index efc10f26e1..e01df1428c 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -160,8 +160,8 @@ namespace osu.Game.Rulesets.Objects.Pooling if (!IsPresent) return false; - bool aliveChanged = base.CheckChildrenLife(); - aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); + bool aliveChanged = lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); + aliveChanged |= base.CheckChildrenLife(); return aliveChanged; } } From f2f5cf19a286821e46ff609f1394c66a485d879e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Aug 2025 14:08:06 +0900 Subject: [PATCH 07/11] Return early to avoid creating mod description strings unnecessarily --- osu.Game/Rulesets/Mods/Mod.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 727db913e2..628098c5b6 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -56,6 +56,9 @@ namespace osu.Game.Rulesets.Mods { var bindable = (IBindable)property.GetValue(this)!; + if (!bindable.IsDefault) + continue; + string valueText; switch (bindable) @@ -69,8 +72,7 @@ namespace osu.Game.Rulesets.Mods break; } - if (!bindable.IsDefault) - yield return (attr.Label, valueText); + yield return (attr.Label, valueText); } } } From e83f3d5e778397b5fbb6fb778209afb70521e70b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Aug 2025 14:08:18 +0900 Subject: [PATCH 08/11] Fix some mods showing tooltips when settings are default --- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 9 ++++++--- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index 22d2f41b82..98a7999065 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs @@ -46,8 +46,10 @@ namespace osu.Game.Rulesets.Mods { get { - yield return ("Roll speed", $"{SpinSpeed.Value:N2} rpm"); - yield return ("Direction", Direction.Value.GetDescription()); + if (!SpinSpeed.IsDefault) + yield return ("Roll speed", $"{SpinSpeed.Value:N2} rpm"); + if (!Direction.IsDefault) + yield return ("Direction", Direction.Value.GetDescription()); } } @@ -55,7 +57,8 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - playfieldAdjustmentContainer.Rotation = CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfieldAdjustmentContainer.Rotation = + CurrentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 8dfe8444e8..049b8f9b7f 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -41,7 +41,8 @@ namespace osu.Game.Rulesets.Mods { get { - yield return ("Speed change", $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"); + if (!InitialRate.IsDefault || !FinalRate.IsDefault) + yield return ("Speed change", $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"); if (!AdjustPitch.IsDefault) yield return ("Adjust pitch", AdjustPitch.Value ? "On" : "Off"); From 9e77a5b0507c7d71fad374c9d59169dbe0ece269 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Aug 2025 16:01:49 +0900 Subject: [PATCH 09/11] Fix obviously incorrect conditional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 628098c5b6..477372b97d 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mods { var bindable = (IBindable)property.GetValue(this)!; - if (!bindable.IsDefault) + if (bindable.IsDefault) continue; string valueText; From 526ee32268fd74a65ebe42fe53bcdb9cfe32fe12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Aug 2025 09:54:42 +0200 Subject: [PATCH 10/11] Apply suggested rename --- .../Navigation/TestScenePresentBeatmap.cs | 2 +- .../BeatmapCarouselFilterGroupingTest.cs | 2 +- .../SongSelectV2/BeatmapCarouselTestScene.cs | 4 +- .../TestSceneBeatmapCarouselFiltering.cs | 8 +-- .../Visual/SongSelectV2/TestScenePanelSet.cs | 8 +-- .../TestSceneSongSelectGrouping.cs | 2 +- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 62 +++++++++---------- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 16 ++--- osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 10 +-- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 6092bdde3a..1dd39e5bf9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("beatmap in song select", () => { var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen; - return songSelect.ChildrenOfType().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetUnderGrouping bsug && bsug.BeatmapSet.MatchesOnlineID(getImport())); + return songSelect.ChildrenOfType().Single().GetCarouselItems()!.Any(i => i.Model is GroupedBeatmapSet gbs && gbs.BeatmapSet.MatchesOnlineID(getImport())); }); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs index efd4eb7b03..32a7b89424 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselFilterGroupingTest.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ]; var results = await runGrouping(GroupMode.None, beatmapSets); - Assert.That(results.Select(r => r.Model).OfType().Select(setUnderGrouping => setUnderGrouping.BeatmapSet), Is.EquivalentTo(beatmapSets)); + Assert.That(results.Select(r => r.Model).OfType().Select(groupedSet => groupedSet.BeatmapSet), 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/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index 2664062fc2..f18e1e9b52 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 // Using groupingFilter.SetItems.Count alone doesn't work. // When sorting by difficulty, there can be more than one set panel for the same set displayed. - return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetUnderGrouping)); + return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is GroupedBeatmapSet)); }, () => Is.EqualTo(expected)); } @@ -440,7 +440,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public BeatmapInfo? SelectedBeatmapInfo => CurrentSelection as BeatmapInfo; public BeatmapSetInfo? SelectedBeatmapSet => SelectedBeatmapInfo?.BeatmapSet; - public new BeatmapSetUnderGrouping? ExpandedBeatmapSet => base.ExpandedBeatmapSet; + public new GroupedBeatmapSet? ExpandedBeatmapSet => base.ExpandedBeatmapSet; public new GroupDefinition? ExpandedGroup => base.ExpandedGroup; public TestBeatmapCarousel() diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs index d599c07f27..687c4c23be 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapCarouselFiltering.cs @@ -397,7 +397,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectNextPanel(); AddAssert("keyboard selected is first set", - () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet, () => Is.EqualTo(BeatmapSets.First())); } @@ -416,7 +416,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectPrevPanel(); AddAssert("keyboard selected is last set", - () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet, () => Is.EqualTo(BeatmapSets.Last())); } @@ -433,7 +433,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectPrevPanel(); AddAssert("keyboard selected is first set", - () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet, () => Is.EqualTo(BeatmapSets.First())); } @@ -451,7 +451,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 // Single result is automatically selected for us, so we iterate once backwards to the set header. SelectPrevPanel(); AddAssert("keyboard selected is second set", - () => (GetKeyboardSelectedPanel()?.Item?.Model as BeatmapSetUnderGrouping)?.BeatmapSet, + () => (GetKeyboardSelectedPanel()?.Item?.Model as GroupedBeatmapSet)?.BeatmapSet, () => Is.EqualTo(BeatmapSets.Last())); } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs index 6a212381a8..b574262d55 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestScenePanelSet.cs @@ -75,21 +75,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { new PanelBeatmapSet { - Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)) + Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)) }, new PanelBeatmapSet { - Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)), + Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)), KeyboardSelected = { Value = true } }, new PanelBeatmapSet { - Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)), + Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)), Expanded = { Value = true } }, new PanelBeatmapSet { - Item = new CarouselItem(new BeatmapSetUnderGrouping(null, beatmapSet)), + Item = new CarouselItem(new GroupedBeatmapSet(null, beatmapSet)), KeyboardSelected = { Value = true }, Expanded = { Value = true } }, diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs index be7f705532..0772607a57 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectGrouping.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("no-collection group present", () => { var group = grouping.GroupItems.Single(g => g.Key.Title == "Not in collection"); - return group.Value.Select(i => i.Model).OfType().Single().BeatmapSet.Equals(beatmapSet); + return group.Value.Select(i => i.Model).OfType().Single().BeatmapSet.Equals(beatmapSet); }); AddStep("add beatmap to collection", () => diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 95fb26c6dd..22079ea91f 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -70,11 +70,11 @@ namespace osu.Game.Screens.SelectV2 if (grouping.BeatmapSetsGroupedTogether) { // Give some space around the expanded beatmap set, at the top.. - if (bottom.Model is BeatmapSetUnderGrouping && bottom.IsExpanded) + if (bottom.Model is GroupedBeatmapSet && bottom.IsExpanded) return SPACING * 2; // ..and the bottom. - if (top.Model is BeatmapInfo && bottom.Model is BeatmapSetUnderGrouping) + if (top.Model is BeatmapInfo && bottom.Model is GroupedBeatmapSet) return SPACING * 2; // Beatmap difficulty panels do not overlap with themselves or any other panel. @@ -207,12 +207,12 @@ namespace osu.Game.Screens.SelectV2 return true; } - if (item.Model is BeatmapSetUnderGrouping setUnderGrouping) + if (item.Model is GroupedBeatmapSet groupedSet) { - if (oldItems.Contains(setUnderGrouping.BeatmapSet)) + if (oldItems.Contains(groupedSet.BeatmapSet)) return false; - RequestRecommendedSelection(setUnderGrouping.BeatmapSet.Beatmaps); + RequestRecommendedSelection(groupedSet.BeatmapSet.Beatmaps); return true; } } @@ -283,7 +283,7 @@ namespace osu.Game.Screens.SelectV2 protected GroupDefinition? ExpandedGroup { get; private set; } - protected BeatmapSetUnderGrouping? ExpandedBeatmapSet { get; private set; } + protected GroupedBeatmapSet? ExpandedBeatmapSet { get; private set; } protected override bool ShouldActivateOnKeyboardSelection(CarouselItem item) => grouping.BeatmapSetsGroupedTogether && item.Model is BeatmapInfo; @@ -311,8 +311,8 @@ namespace osu.Game.Screens.SelectV2 return; - case BeatmapSetUnderGrouping setUnderGrouping: - selectRecommendedDifficultyForBeatmapSet(setUnderGrouping); + case GroupedBeatmapSet groupedSet: + selectRecommendedDifficultyForBeatmapSet(groupedSet); return; case BeatmapInfo beatmapInfo: @@ -338,7 +338,7 @@ namespace osu.Game.Screens.SelectV2 switch (model) { - case BeatmapSetUnderGrouping: + case GroupedBeatmapSet: case GroupDefinition: throw new InvalidOperationException("Groups should never become selected"); @@ -349,7 +349,7 @@ namespace osu.Game.Screens.SelectV2 setExpandedGroup(containingGroup); if (grouping.BeatmapSetsGroupedTogether) - setExpandedSet(new BeatmapSetUnderGrouping(containingGroup, beatmapInfo.BeatmapSet!)); + setExpandedSet(new GroupedBeatmapSet(containingGroup, beatmapInfo.BeatmapSet!)); break; } } @@ -373,10 +373,10 @@ namespace osu.Game.Screens.SelectV2 setExpandedGroup(groupForReselection); } - private void selectRecommendedDifficultyForBeatmapSet(BeatmapSetUnderGrouping setUnderGrouping) + private void selectRecommendedDifficultyForBeatmapSet(GroupedBeatmapSet set) { // Selecting a set isn't valid – let's re-select the first visible difficulty. - if (grouping.SetItems.TryGetValue(setUnderGrouping, out var items)) + if (grouping.SetItems.TryGetValue(set, out var items)) { var beatmaps = items.Select(i => i.Model).OfType(); RequestRecommendedSelection(beatmaps); @@ -424,7 +424,7 @@ namespace osu.Game.Screens.SelectV2 { switch (item.Model) { - case BeatmapSetUnderGrouping: + case GroupedBeatmapSet: return true; case BeatmapInfo: @@ -463,11 +463,11 @@ namespace osu.Game.Screens.SelectV2 i.IsExpanded = true; break; - case BeatmapSetUnderGrouping setUnderGrouping: + case GroupedBeatmapSet groupedSet: // Case where there are set headers, header should be visible // and items should use the set's expanded state. i.IsVisible = true; - setExpansionStateOfSetItems(setUnderGrouping, i.IsExpanded); + setExpansionStateOfSetItems(groupedSet, i.IsExpanded); break; default: @@ -497,21 +497,21 @@ namespace osu.Game.Screens.SelectV2 } } - private void setExpandedSet(BeatmapSetUnderGrouping setUnderGrouping) + private void setExpandedSet(GroupedBeatmapSet set) { if (ExpandedBeatmapSet != null) setExpansionStateOfSetItems(ExpandedBeatmapSet, false); - ExpandedBeatmapSet = setUnderGrouping; + ExpandedBeatmapSet = set; setExpansionStateOfSetItems(ExpandedBeatmapSet, true); } - private void setExpansionStateOfSetItems(BeatmapSetUnderGrouping set, bool expanded) + private void setExpansionStateOfSetItems(GroupedBeatmapSet set, bool expanded) { if (grouping.SetItems.TryGetValue(set, out var items)) { foreach (var i in items) { - if (i.Model is BeatmapSetUnderGrouping) + if (i.Model is GroupedBeatmapSet) i.IsExpanded = expanded; else i.IsVisible = expanded; @@ -549,7 +549,7 @@ namespace osu.Game.Screens.SelectV2 sampleToggleGroup?.Play(); return; - case BeatmapSetUnderGrouping: + case GroupedBeatmapSet: sampleChangeSet?.Play(); return; @@ -688,8 +688,8 @@ namespace osu.Game.Screens.SelectV2 // it is doing a Replace operation on the list. If it is, then check the local handling in beatmapSetsChanged // before changing matching requirements here. - if (x is BeatmapSetUnderGrouping setUnderGroupingX && y is BeatmapSetUnderGrouping setUnderGroupingY) - return setUnderGroupingX.Equals(setUnderGroupingY); + if (x is GroupedBeatmapSet groupedSetX && y is GroupedBeatmapSet groupedSetY) + return groupedSetX.Equals(groupedSetY); if (x is BeatmapInfo beatmapX && y is BeatmapInfo beatmapY) return beatmapX.Equals(beatmapY); @@ -719,7 +719,7 @@ namespace osu.Game.Screens.SelectV2 return beatmapPanelPool.Get(); - case BeatmapSetUnderGrouping: + case GroupedBeatmapSet: return setPanelPool.Get(); } @@ -829,31 +829,31 @@ namespace osu.Game.Screens.SelectV2 private bool nextRandomSet() { - ICollection visibleSetsUnderGrouping = ExpandedGroup != null + ICollection visibleSetsUnderGrouping = ExpandedGroup != null // In the case of grouping, users expect random to only operate on the expanded group. // This is going to incur some overhead as we don't have a group-beatmapset mapping currently. // // If this becomes an issue, we could either store a mapping, or run the random algorithm many times // using the `SetItems` method until we get a group HIT. - ? grouping.GroupItems[ExpandedGroup].Select(i => i.Model).OfType().ToArray() + ? grouping.GroupItems[ExpandedGroup].Select(i => i.Model).OfType().ToArray() // This is the fastest way to retrieve sets for randomisation. : grouping.SetItems.Keys; - BeatmapSetUnderGrouping set; + GroupedBeatmapSet set; switch (randomAlgorithm.Value) { case RandomSelectAlgorithm.RandomPermutation: { - ICollection notYetVisitedSets = - visibleSetsUnderGrouping.ExceptBy(previouslyVisitedRandomBeatmaps.Select(b => b.BeatmapSet!), setUnderGrouping => setUnderGrouping.BeatmapSet).ToList(); + ICollection notYetVisitedSets = + visibleSetsUnderGrouping.ExceptBy(previouslyVisitedRandomBeatmaps.Select(b => b.BeatmapSet!), groupedSet => groupedSet.BeatmapSet).ToList(); if (!notYetVisitedSets.Any()) { - previouslyVisitedRandomBeatmaps.RemoveAll(b => visibleSetsUnderGrouping.Any(setUnderGrouping => setUnderGrouping.BeatmapSet.Equals(b.BeatmapSet!))); + previouslyVisitedRandomBeatmaps.RemoveAll(b => visibleSetsUnderGrouping.Any(groupedSet => groupedSet.BeatmapSet.Equals(b.BeatmapSet!))); notYetVisitedSets = visibleSetsUnderGrouping; if (CurrentSelection is BeatmapInfo beatmapInfo) - notYetVisitedSets = notYetVisitedSets.ExceptBy([beatmapInfo.BeatmapSet!], setUnderGrouping => setUnderGrouping.BeatmapSet).ToList(); + notYetVisitedSets = notYetVisitedSets.ExceptBy([beatmapInfo.BeatmapSet!], groupedSet => groupedSet.BeatmapSet).ToList(); } if (notYetVisitedSets.Count == 0) @@ -966,5 +966,5 @@ namespace osu.Game.Screens.SelectV2 /// Used to represent a portion of a under a . /// The purpose of this model is to support splitting beatmap sets apart when the active grouping mode demands it. /// - public record BeatmapSetUnderGrouping([UsedImplicitly] GroupDefinition? Group, BeatmapSetInfo BeatmapSet); + public record GroupedBeatmapSet([UsedImplicitly] GroupDefinition? Group, BeatmapSetInfo BeatmapSet); } diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index 63bc94b087..0d2489c304 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -29,14 +29,14 @@ namespace osu.Game.Screens.SelectV2 /// /// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection. /// - public IDictionary> SetItems => setMap; + public IDictionary> SetItems => setMap; /// /// Groups contain children which are group-selectable. This dictionary holds the relationships between groups-panels to allow expanding them on selection. /// public IDictionary> GroupItems => groupMap; - private Dictionary> setMap = new Dictionary>(); + private Dictionary> setMap = new Dictionary>(); private Dictionary> groupMap = new Dictionary>(); private readonly Func getCriteria; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.SelectV2 return await Task.Run(() => { // preallocate space for the new mappings using last known estimates - var newSetMap = new Dictionary>(setMap.Count); + var newSetMap = new Dictionary>(setMap.Count); var newGroupMap = new Dictionary>(groupMap.Count); var criteria = getCriteria(); @@ -94,12 +94,12 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)item.Model; bool newBeatmapSet = lastBeatmap?.BeatmapSet!.ID != beatmap.BeatmapSet!.ID; - var beatmapSetUnderGrouping = new BeatmapSetUnderGrouping(group, beatmap.BeatmapSet!); + var groupedBeatmapSet = new GroupedBeatmapSet(group, beatmap.BeatmapSet!); if (newBeatmapSet) { - if (!newSetMap.TryGetValue(beatmapSetUnderGrouping, out currentSetItems)) - newSetMap[beatmapSetUnderGrouping] = currentSetItems = new HashSet(); + if (!newSetMap.TryGetValue(groupedBeatmapSet, out currentSetItems)) + newSetMap[groupedBeatmapSet] = currentSetItems = new HashSet(); } if (BeatmapSetsGroupedTogether) @@ -109,7 +109,7 @@ namespace osu.Game.Screens.SelectV2 if (groupItem != null) groupItem.NestedItemCount++; - addItem(new CarouselItem(beatmapSetUnderGrouping) + addItem(new CarouselItem(groupedBeatmapSet) { DrawHeight = PanelBeatmapSet.HEIGHT, DepthLayer = -1 @@ -136,7 +136,7 @@ namespace osu.Game.Screens.SelectV2 currentGroupItems?.Add(i); currentSetItems?.Add(i); - i.IsVisible = i.Model is GroupDefinition || (group == null && (i.Model is BeatmapSetUnderGrouping || !BeatmapSetsGroupedTogether)); + i.IsVisible = i.Model is GroupDefinition || (group == null && (i.Model is GroupedBeatmapSet || !BeatmapSetsGroupedTogether)); } } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index 7b07076975..1a6e886cb7 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -67,12 +67,12 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private IBindable ruleset { get; set; } = null!; - private BeatmapSetUnderGrouping beatmapSetUnderGrouping + private GroupedBeatmapSet groupedBeatmapSet { get { Debug.Assert(Item != null); - return (BeatmapSetUnderGrouping)Item!.Model; + return (GroupedBeatmapSet)Item!.Model; } } @@ -188,7 +188,7 @@ namespace osu.Game.Screens.SelectV2 { base.PrepareForUse(); - var beatmapSet = beatmapSetUnderGrouping.BeatmapSet; + var beatmapSet = groupedBeatmapSet.BeatmapSet; // Choice of background image matches BSS implementation (always uses the lowest `beatmap_id` from the set). setBackground.Beatmap = beatmaps.GetWorkingBeatmap(beatmapSet.Beatmaps.MinBy(b => b.OnlineID)); @@ -222,7 +222,7 @@ namespace osu.Game.Screens.SelectV2 if (Item == null) return Array.Empty(); - var beatmapSet = beatmapSetUnderGrouping.BeatmapSet; + var beatmapSet = groupedBeatmapSet.BeatmapSet; List items = new List(); @@ -275,7 +275,7 @@ namespace osu.Game.Screens.SelectV2 private MenuItem createCollectionMenuItem(BeatmapCollection collection) { - var beatmapSet = beatmapSetUnderGrouping.BeatmapSet; + var beatmapSet = groupedBeatmapSet.BeatmapSet; TernaryState state; From 0a408a3ac4c1eec91f662bd04a297a4683eeb525 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Aug 2025 17:11:47 +0900 Subject: [PATCH 11/11] Fix tournament test failure due to control change --- osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs index 31583bf8b7..eb9faa5930 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Tournament.Components; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Screens.Gameplay; @@ -66,6 +67,6 @@ namespace osu.Game.Tournament.Tests.Screens () => this.ChildrenOfType().All(score => score.Alpha == (visible ? 1 : 0))); private void toggleWarmup() - => AddStep("toggle warmup", () => this.ChildrenOfType().First().TriggerClick()); + => AddStep("toggle warmup", () => this.ChildrenOfType().First().ChildrenOfType().First().TriggerClick()); } }