From 0c76c5297d5107ec5757b709a9892a1bce8c6173 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 18:50:32 +0900 Subject: [PATCH 1/8] Add test coverage of eager selection behaviours --- .../SongSelectV2/TestSceneSongSelect.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index fcb74e539b..458f81c7af 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Mods; @@ -471,6 +472,50 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddUntilStep("popover displayed", () => this.ChildrenOfType().Any(p => p.IsPresent)); } + [Test] + public void TestSelectionChangedFromProtectedToNone() + { + ImportBeatmapForRuleset(0); + AddStep("set protected on import", () => Realm.Write(r => r.All().First(s => !s.DeletePending).Protected = true)); + + AddStep("selected protected", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().First(s => s.Protected).Beatmaps.First())); + + LoadSongSelect(); + + AddUntilStep("beatmap deselected", () => Beatmap.IsDefault); + } + + [Test] + public void TestSelectionChangedFromProtectedToSomething() + { + ImportBeatmapForRuleset(0); + AddStep("set protected on import", () => Realm.Write(r => r.All().First(s => !s.DeletePending).Protected = true)); + + AddStep("selected protected", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().First(s => s.Protected).Beatmaps.First())); + + ImportBeatmapForRuleset(0); + + LoadSongSelect(); + + AddUntilStep("beatmap selected", () => !Beatmap.IsDefault); + AddUntilStep("selection not protected", () => !Beatmap.Value.BeatmapSetInfo.Protected); + } + + [Test] + public void TestSelectAfterDeletion() + { + LoadSongSelect(); + + ImportBeatmapForRuleset(0); + AddUntilStep("beatmap selected", () => !Beatmap.IsDefault); + + AddStep("delete all beatmaps", () => Beatmaps.Delete()); + AddUntilStep("beatmap not selected", () => Beatmap.IsDefault); + + AddStep("restore deleted", () => Beatmaps.UndeleteAll()); + AddUntilStep("beatmap selected", () => !Beatmap.IsDefault); + } + [Test] public void TestFooterOptionsState() { From f3782acddd2df6c1612a526bbd63824d53c035cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 03:21:43 +0900 Subject: [PATCH 2/8] Fix `BeatmapSetInfo` missing `GetHashCode` implentation Causing dictionary lookups to fail when expecting equality implementation to work. --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 59e413d935..0aad55b26d 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps return ID == other.ID; } + public override int GetHashCode() + { + // ReSharper disable once NonReadonlyMemberInGetHashCode + return ID.GetHashCode(); + } + public override string ToString() => Metadata.GetDisplayString(); public bool Equals(IBeatmapSetInfo? other) => other is BeatmapSetInfo b && Equals(b); From c1e1bfde0a2c0b9d1b0069ff31fe8573d30c929a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 17:14:12 +0900 Subject: [PATCH 3/8] Hook up carousel to song select to global beatmap --- .../SongSelectV2/BeatmapCarouselTestScene.cs | 3 +- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 15 ++- osu.Game/Screens/SelectV2/PanelBeatmap.cs | 2 +- .../SelectV2/PanelBeatmapStandalone.cs | 2 +- osu.Game/Screens/SelectV2/SongSelect.cs | 115 ++++++++++++++++-- 5 files changed, 121 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index 0f991abcfc..0eb130a5da 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -108,7 +108,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Carousel = new TestBeatmapCarousel { NewItemsPresented = () => NewItemsPresentedInvocationCount++, - ChooseRecommendedBeatmap = beatmaps => BeatmapRecommendationFunction?.Invoke(beatmaps) ?? beatmaps.First(), + RequestSelection = b => Carousel.CurrentSelection = b, + RequestRecommendedSelection = beatmaps => Carousel.CurrentSelection = BeatmapRecommendationFunction?.Invoke(beatmaps) ?? beatmaps.First(), BleedTop = 50, BleedBottom = 50, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 0d84dea605..9e7ac00375 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -27,9 +27,14 @@ namespace osu.Game.Screens.SelectV2 public Action? RequestPresentBeatmap { private get; init; } /// - /// From the provided beatmaps, return the most appropriate one for the user's skill. + /// From the provided beatmaps, select the most appropriate one for the user's skill. /// - public Func, BeatmapInfo>? ChooseRecommendedBeatmap { private get; init; } + public required Action> RequestRecommendedSelection { private get; init; } + + /// + /// Selection requested for the provided beatmap. + /// + public required Action RequestSelection { private get; init; } public const float SPACING = 3f; @@ -139,7 +144,7 @@ namespace osu.Game.Screens.SelectV2 // TODO: should this exist in song select instead of here? // we need to ensure the global beatmap is also updated alongside changes. if (CurrentSelection != null && CheckModelEquality(beatmap, CurrentSelection)) - CurrentSelection = matchingNewBeatmap; + RequestSelection(matchingNewBeatmap); Items.ReplaceRange(previousIndex, 1, [matchingNewBeatmap]); newSetBeatmaps.Remove(matchingNewBeatmap); @@ -190,7 +195,7 @@ namespace osu.Game.Screens.SelectV2 if (grouping.SetItems.TryGetValue(setInfo, out var items)) { var beatmaps = items.Select(i => i.Model).OfType(); - CurrentSelection = ChooseRecommendedBeatmap?.Invoke(beatmaps) ?? beatmaps.First(); + RequestRecommendedSelection(beatmaps); } return; @@ -202,7 +207,7 @@ namespace osu.Game.Screens.SelectV2 return; } - CurrentSelection = beatmapInfo; + RequestSelection(beatmapInfo); return; } } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmap.cs b/osu.Game/Screens/SelectV2/PanelBeatmap.cs index 497fce17ca..b129cd683f 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmap.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmap.cs @@ -209,7 +209,7 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; - starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, 200); + starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true); } diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs index 6c0779bab6..96e8fa47ff 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapStandalone.cs @@ -245,7 +245,7 @@ namespace osu.Game.Screens.SelectV2 var beatmap = (BeatmapInfo)Item.Model; - starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, 200); + starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, starDifficultyCancellationSource.Token, SongSelect.SELECTION_DEBOUNCE); starDifficultyBindable.BindValueChanged(_ => updateDisplay(), true); } diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 4e85d0a3eb..6f6eda16fa 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -15,6 +16,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -49,6 +51,10 @@ namespace osu.Game.Screens.SelectV2 [Cached(typeof(ISongSelect))] public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler, ISongSelect { + // this is intentionally slightly higher than key repeat, but low enough to not impeded user experience. + // this avoids rapid churn loading when iterating the carousel using keyboard. + public const int SELECTION_DEBOUNCE = 100; + private const float logo_scale = 0.4f; private const double fade_duration = 300; @@ -56,6 +62,12 @@ namespace osu.Game.Screens.SelectV2 public const float CORNER_RADIUS_HIDE_OFFSET = 20f; public const float ENTER_DURATION = 600; + /// + /// Whether this song select instance should take control of the global track, + /// applying looping and preview offsets. + /// + protected bool ControlGlobalMusic { get; init; } = true; + private readonly ModSelectOverlay modSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Aquamarine) { ShowPresets = true, @@ -177,9 +189,11 @@ namespace osu.Game.Screens.SelectV2 { BleedTop = FilterControl.HEIGHT_FROM_SCREEN_TOP + 5, BleedBottom = ScreenFooter.HEIGHT + 5, - RequestPresentBeatmap = SelectAndStart, - NewItemsPresented = newItemsPresented, RelativeSizeAxes = Axes.Both, + RequestPresentBeatmap = _ => OnStart(), + RequestSelection = selectBeatmap, + RequestRecommendedSelection = beatmaps => { selectBeatmap(difficultyRecommender?.GetRecommendedBeatmap(beatmaps) ?? beatmaps.First()); }, + NewItemsPresented = newItemsPresented, }, noResultsPlaceholder = new NoResultsPlaceholder(), } @@ -245,10 +259,88 @@ namespace osu.Game.Screens.SelectV2 detailsArea.Height = wedgesContainer.DrawHeight - titleWedge.LayoutSize.Y - 4; } + #region Audio + + [Resolved] + private MusicController music { get; set; } = null!; + + private readonly WeakReference lastTrack = new WeakReference(null); + + /// + /// Ensures some music is playing for the current track. + /// Will resume playback from a manual user pause if the track has changed. + /// + private void ensurePlayingSelected() + { + if (!ControlGlobalMusic) + return; + + ITrack track = music.CurrentTrack; + + bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; + + if (!track.IsRunning && (music.UserPauseRequested != true || isNewTrack)) + { + Logger.Log($"Song select decided to {nameof(ensurePlayingSelected)}"); + music.Play(true); + } + + lastTrack.SetTarget(track); + } + + private bool isHandlingLooping; + + private void beginLooping() + { + if (!ControlGlobalMusic) + return; + + Debug.Assert(!isHandlingLooping); + + isHandlingLooping = true; + + ensureTrackLooping(Beatmap.Value, TrackChangeDirection.None); + + music.TrackChanged += ensureTrackLooping; + } + + private void endLooping() + { + // may be called multiple times during screen exit process. + if (!isHandlingLooping) + return; + + music.CurrentTrack.Looping = isHandlingLooping = false; + + music.TrackChanged -= ensureTrackLooping; + } + + private void ensureTrackLooping(IWorkingBeatmap beatmap, TrackChangeDirection changeDirection) + => beatmap.PrepareTrackForPreview(true); + + #endregion + #region Selection handling - private BeatmapInfo getRecommendedBeatmap(IEnumerable beatmaps) - => difficultyRecommender?.GetRecommendedBeatmap(beatmaps) ?? beatmaps.First(); + private ScheduledDelegate? selectionDebounce; + + private void selectBeatmap(BeatmapInfo beatmap) + { + carousel.CurrentSelection = beatmap; + + selectionDebounce?.Cancel(); + selectionDebounce = Scheduler.AddDelayed(() => selectBeatmap(beatmaps.GetWorkingBeatmap(beatmap)), SELECTION_DEBOUNCE); + } + + private void selectBeatmap(WorkingBeatmap beatmap) + { + carousel.CurrentSelection = beatmap.BeatmapInfo; + + Beatmap.Value = beatmap; + + if (this.IsCurrentScreen()) + ensurePlayingSelected(); + } #endregion @@ -266,6 +358,10 @@ namespace osu.Game.Screens.SelectV2 modSelectOverlay.Beatmap.BindTo(Beatmap); modSelectOverlay.SelectedMods.BindTo(Mods); + + selectBeatmap(Beatmap.Value); + + beginLooping(); } public override void OnResuming(ScreenTransitionEvent e) @@ -285,6 +381,8 @@ namespace osu.Game.Screens.SelectV2 // required due to https://github.com/ppy/osu-framework/issues/3218 modSelectOverlay.SelectedMods.Disabled = false; modSelectOverlay.SelectedMods.BindTo(Mods); + + beginLooping(); } public override void OnSuspending(ScreenTransitionEvent e) @@ -300,6 +398,8 @@ namespace osu.Game.Screens.SelectV2 carousel.VisuallyFocusSelected = true; + endLooping(); + base.OnSuspending(e); } @@ -311,6 +411,8 @@ namespace osu.Game.Screens.SelectV2 detailsArea.Hide(); filterControl.Hide(); + endLooping(); + return base.OnExiting(e); } @@ -368,10 +470,7 @@ namespace osu.Game.Screens.SelectV2 private void criteriaChanged(FilterCriteria criteria) { filterDebounce?.Cancel(); - filterDebounce = Scheduler.AddDelayed(() => - { - carousel.Filter(criteria); - }, filter_delay); + filterDebounce = Scheduler.AddDelayed(() => { carousel.Filter(criteria); }, filter_delay); } private void newItemsPresented() From 4a03240ee6297dc12acee51b2c78b03361aeac3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 17:58:58 +0900 Subject: [PATCH 4/8] Add basic "eager" selection to ensure something is selected at song select after import --- .../SongSelectV2/BeatmapCarouselTestScene.cs | 2 +- osu.Game/Graphics/Carousel/Carousel.cs | 4 ++-- osu.Game/Screens/SelectV2/SongSelect.cs | 20 +++++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs index 0eb130a5da..f92abd1063 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/BeatmapCarouselTestScene.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { Carousel = new TestBeatmapCarousel { - NewItemsPresented = () => NewItemsPresentedInvocationCount++, + NewItemsPresented = _ => NewItemsPresentedInvocationCount++, RequestSelection = b => Carousel.CurrentSelection = b, RequestRecommendedSelection = beatmaps => Carousel.CurrentSelection = BeatmapRecommendationFunction?.Invoke(beatmaps) ?? beatmaps.First(), BleedTop = 50, diff --git a/osu.Game/Graphics/Carousel/Carousel.cs b/osu.Game/Graphics/Carousel/Carousel.cs index 37d69dab89..31c95f6930 100644 --- a/osu.Game/Graphics/Carousel/Carousel.cs +++ b/osu.Game/Graphics/Carousel/Carousel.cs @@ -39,7 +39,7 @@ namespace osu.Game.Graphics.Carousel /// /// Called after a filter operation or change in items results in the visible carousel items changing. /// - public Action? NewItemsPresented { private get; init; } + public Action>? NewItemsPresented { private get; init; } /// /// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it. @@ -318,7 +318,7 @@ namespace osu.Game.Graphics.Carousel if (!Scroll.UserScrolling) scrollToSelection(); - NewItemsPresented?.Invoke(); + NewItemsPresented?.Invoke(carouselItems); }); return items; diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 6f6eda16fa..401470ccfc 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.Graphics.Carousel; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -192,7 +193,7 @@ namespace osu.Game.Screens.SelectV2 RelativeSizeAxes = Axes.Both, RequestPresentBeatmap = _ => OnStart(), RequestSelection = selectBeatmap, - RequestRecommendedSelection = beatmaps => { selectBeatmap(difficultyRecommender?.GetRecommendedBeatmap(beatmaps) ?? beatmaps.First()); }, + RequestRecommendedSelection = selectRecommendedBeatmap, NewItemsPresented = newItemsPresented, }, noResultsPlaceholder = new NoResultsPlaceholder(), @@ -324,6 +325,11 @@ namespace osu.Game.Screens.SelectV2 private ScheduledDelegate? selectionDebounce; + private void selectRecommendedBeatmap(IEnumerable beatmaps) + { + selectBeatmap(difficultyRecommender?.GetRecommendedBeatmap(beatmaps) ?? beatmaps.First()); + } + private void selectBeatmap(BeatmapInfo beatmap) { carousel.CurrentSelection = beatmap; @@ -473,7 +479,7 @@ namespace osu.Game.Screens.SelectV2 filterDebounce = Scheduler.AddDelayed(() => { carousel.Filter(criteria); }, filter_delay); } - private void newItemsPresented() + private void newItemsPresented(IEnumerable carouselItems) { int count = carousel.MatchedBeatmapsCount; @@ -488,6 +494,16 @@ namespace osu.Game.Screens.SelectV2 // Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918 // but also in this case we want support for formatting a number within a string). filterControl.StatusText = count != 1 ? $"{count:#,0} matches" : $"{count:#,0} match"; + + if (!carouselItems.Any()) + { + Beatmap.SetDefault(); + return; + } + + if (Beatmap.IsDefault || Beatmap.Value.BeatmapSetInfo?.DeletePending == true) + // TODO: this should probably use random, not recommended like this. + selectRecommendedBeatmap(carouselItems.Select(i => i.Model).OfType()); } #endregion From e33b92fa5cf8c8d71c67e82bf9aa9f25017db3f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 18:02:34 +0900 Subject: [PATCH 5/8] Update tests to no longer require local selection --- .../SongSelectV2/TestSceneSongSelect.cs | 36 ------------------- .../TestSceneSongSelectFiltering.cs | 5 --- 2 files changed, 41 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index 458f81c7af..1534b1174b 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -37,10 +37,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("beatmap imported", () => Beatmaps.GetAllUsableBeatmapSets().Any(), () => Is.True); - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddAssert("beatmap selected", () => !Beatmap.IsDefault); AddStep("import score", () => @@ -91,11 +87,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ImportBeatmapForRuleset(0); AddAssert("beatmap imported", () => Beatmaps.GetAllUsableBeatmapSets().Any(), () => Is.True); - - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddAssert("beatmap selected", () => !Beatmap.IsDefault); AddStep("press shift-delete", () => @@ -254,11 +245,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ImportBeatmapForRuleset(0); LoadSongSelect(); - - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddStep("press right", () => InputManager.Key(Key.Right)); // press right to select in carousel, also remove. AddAssert("beatmap selected", () => !Beatmap.IsDefault); @@ -284,11 +270,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ImportBeatmapForRuleset(0); LoadSongSelect(); - - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddStep("press right", () => InputManager.Key(Key.Right)); // press right to select in carousel, also remove. AddAssert("beatmap selected", () => !Beatmap.IsDefault); @@ -316,11 +297,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ImportBeatmapForRuleset(0); LoadSongSelect(); - - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddStep("press right", () => InputManager.Key(Key.Right)); // press right to select in carousel, also remove. AddAssert("beatmap selected", () => !Beatmap.IsDefault); @@ -461,11 +437,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 LoadSongSelect(); ImportBeatmapForRuleset(0); - - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddAssert("options enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("click", () => this.ChildrenOfType().Single().TriggerClick()); @@ -523,16 +494,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 ImportBeatmapForRuleset(0); - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); - AddAssert("options enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("delete all beatmaps", () => Beatmaps.Delete()); - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. AddAssert("beatmap selected", () => !Beatmap.IsDefault); AddStep("select no beatmap", () => Beatmap.SetDefault()); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectFiltering.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectFiltering.cs index c0c80e0bc3..9532895edd 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectFiltering.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectFiltering.cs @@ -254,11 +254,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 checkMatchedBeatmaps(3); - // song select should automatically select the beatmap for us but this is not implemented yet. - // todo: remove when that's the case. - AddAssert("no beatmap selected", () => Beatmap.IsDefault); - AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); - AddStep("hide", () => Beatmaps.Hide(Beatmap.Value.BeatmapInfo)); checkMatchedBeatmaps(2); From d0a1b40a84b3fe8409e625a02185d67a4f43c6c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 18:55:00 +0900 Subject: [PATCH 6/8] Fix beatmap background not being used at new song select --- osu.Game/Screens/SelectV2/SongSelect.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 401470ccfc..26b1714a79 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -35,6 +35,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Skinning; @@ -50,7 +51,7 @@ namespace osu.Game.Screens.SelectV2 /// This will be gradually built upon and ultimately replace once everything is in place. /// [Cached(typeof(ISongSelect))] - public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler, ISongSelect + public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBindingHandler, ISongSelect { // this is intentionally slightly higher than key repeat, but low enough to not impeded user experience. // this avoids rapid churn loading when iterating the carousel using keyboard. @@ -346,6 +347,17 @@ namespace osu.Game.Screens.SelectV2 if (this.IsCurrentScreen()) ensurePlayingSelected(); + + // If not the current screen, this will be applied in OnResuming. + if (this.IsCurrentScreen()) + { + ApplyToBackground(backgroundModeBeatmap => + { + backgroundModeBeatmap.Beatmap = beatmap; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.FadeColour(Color4.White, 250); + }); + } } #endregion From 5212c27a7109060a967dab19764372be005cebfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 May 2025 19:04:01 +0900 Subject: [PATCH 7/8] Ensure protected beatmap is reselected on entering screen --- osu.Game/Screens/SelectV2/SongSelect.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 26b1714a79..31f37a45e9 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -341,6 +341,9 @@ namespace osu.Game.Screens.SelectV2 private void selectBeatmap(WorkingBeatmap beatmap) { + if (beatmap.BeatmapInfo.BeatmapSet!.Protected) + return; + carousel.CurrentSelection = beatmap.BeatmapInfo; Beatmap.Value = beatmap; @@ -377,9 +380,13 @@ namespace osu.Game.Screens.SelectV2 modSelectOverlay.Beatmap.BindTo(Beatmap); modSelectOverlay.SelectedMods.BindTo(Mods); - selectBeatmap(Beatmap.Value); - beginLooping(); + + // force reselection if entering song select with a protected beatmap + if (Beatmap.Value.BeatmapInfo.BeatmapSet!.Protected) + Beatmap.SetDefault(); + else + selectBeatmap(Beatmap.Value); } public override void OnResuming(ScreenTransitionEvent e) @@ -401,6 +408,11 @@ namespace osu.Game.Screens.SelectV2 modSelectOverlay.SelectedMods.BindTo(Mods); beginLooping(); + + if (Beatmap.Value.BeatmapInfo.BeatmapSet!.Protected) + Beatmap.SetDefault(); + else + selectBeatmap(Beatmap.Value); } public override void OnSuspending(ScreenTransitionEvent e) From 72bd8b2203bbbe69819ebe32e0341af7bcd5a3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 May 2025 14:08:56 +0200 Subject: [PATCH 8/8] Fix grammar in comment --- osu.Game/Screens/SelectV2/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 31f37a45e9..93986d5a77 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.SelectV2 [Cached(typeof(ISongSelect))] public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBindingHandler, ISongSelect { - // this is intentionally slightly higher than key repeat, but low enough to not impeded user experience. + // this is intentionally slightly higher than key repeat, but low enough to not impede user experience. // this avoids rapid churn loading when iterating the carousel using keyboard. public const int SELECTION_DEBOUNCE = 100;