From 4ec4923c175093e530c63a280a1f4080e5cb1013 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 14 May 2025 21:52:41 +0300 Subject: [PATCH 1/4] Open results screen when clicking leaderboard scores --- .../Screens/SelectV2/BeatmapLeaderboardWedge.cs | 11 +++++++++++ osu.Game/Screens/SelectV2/SoloSongSelect.cs | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index b8c4d07d04..fc823c0ebc 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -50,6 +50,9 @@ namespace osu.Game.Screens.SelectV2 [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + [Resolved] + private SongSelect? songSelect { get; set; } + private Container placeholderContainer = null!; private Placeholder? placeholder; @@ -244,6 +247,7 @@ namespace osu.Game.Screens.SelectV2 Rank = i + 1, IsPersonalBest = s.OnlineID == userScore?.OnlineID, SelectedMods = { BindTarget = mods }, + Action = () => onLeaderboardScoreClicked(s), }), loadedScores => { int delay = 200; @@ -279,6 +283,7 @@ namespace osu.Game.Screens.SelectV2 IsPersonalBest = true, Rank = userScore.Position, SelectedMods = { BindTarget = mods }, + Action = () => onLeaderboardScoreClicked(userScore), }; scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding { Bottom = personal_best_height }, 300, Easing.OutQuint); @@ -308,6 +313,12 @@ namespace osu.Game.Screens.SelectV2 scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding(), 300, Easing.OutQuint); } + private void onLeaderboardScoreClicked(ScoreInfo score) + { + if (songSelect is SoloSongSelect soloSongSelect) + soloSongSelect.PresentScore(score); + } + private LeaderboardState displayedState; protected void SetState(LeaderboardState state) diff --git a/osu.Game/Screens/SelectV2/SoloSongSelect.cs b/osu.Game/Screens/SelectV2/SoloSongSelect.cs index 7d62af8c9c..14180fc695 100644 --- a/osu.Game/Screens/SelectV2/SoloSongSelect.cs +++ b/osu.Game/Screens/SelectV2/SoloSongSelect.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Screens; @@ -10,7 +11,9 @@ using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; using osu.Game.Utils; namespace osu.Game.Screens.SelectV2 @@ -25,6 +28,18 @@ namespace osu.Game.Screens.SelectV2 public override bool EditingAllowed => true; + /// + /// Opens results screen with the given score. + /// This assumes active beatmap and ruleset selection matches the score. + /// + public void PresentScore(ScoreInfo score) + { + Debug.Assert(Beatmap.Value.BeatmapInfo.Equals(score.BeatmapInfo)); + Debug.Assert(Ruleset.Value.Equals(score.Ruleset)); + + this.Push(new SoloResultsScreen(score)); + } + protected override bool OnStart() { if (playerLoader != null) return false; From bc35b58db6a601d7d3fb5c5a732f57072073d62c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 17:58:01 +0900 Subject: [PATCH 2/4] Rename new song select interface to be more generic and add score presentation to it --- .../SelectV2/BeatmapLeaderboardWedge.cs | 8 ++------ .../Screens/SelectV2/FooterButtonOptions.cs | 4 ++-- .../SelectV2/FooterButtonOptions_Popover.cs | 16 ++++++++-------- ...SelectBeatmapActions.cs => ISongSelect.cs} | 8 +++++++- osu.Game/Screens/SelectV2/SoloSongSelect.cs | 15 --------------- osu.Game/Screens/SelectV2/SongSelect.cs | 19 +++++++++++++++++-- 6 files changed, 36 insertions(+), 34 deletions(-) rename osu.Game/Screens/SelectV2/{ISongSelectBeatmapActions.cs => ISongSelect.cs} (87%) diff --git a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs index fc823c0ebc..036bacb5e9 100644 --- a/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs +++ b/osu.Game/Screens/SelectV2/BeatmapLeaderboardWedge.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.SelectV2 private OverlayColourProvider colourProvider { get; set; } = null!; [Resolved] - private SongSelect? songSelect { get; set; } + private ISongSelect? songSelect { get; set; } private Container placeholderContainer = null!; private Placeholder? placeholder; @@ -313,11 +313,7 @@ namespace osu.Game.Screens.SelectV2 scoresScroll.TransformTo(nameof(scoresScroll.Padding), new MarginPadding(), 300, Easing.OutQuint); } - private void onLeaderboardScoreClicked(ScoreInfo score) - { - if (songSelect is SoloSongSelect soloSongSelect) - soloSongSelect.PresentScore(score); - } + private void onLeaderboardScoreClicked(ScoreInfo score) => songSelect?.PresentScore(score); private LeaderboardState displayedState; diff --git a/osu.Game/Screens/SelectV2/FooterButtonOptions.cs b/osu.Game/Screens/SelectV2/FooterButtonOptions.cs index c7800b44c3..5b646312d2 100644 --- a/osu.Game/Screens/SelectV2/FooterButtonOptions.cs +++ b/osu.Game/Screens/SelectV2/FooterButtonOptions.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.SelectV2 private IBindable beatmap { get; set; } = null!; [Resolved] - private ISongSelectBeatmapActions? beatmapActions { get; set; } + private ISongSelect? songSelect { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colour) @@ -51,7 +51,7 @@ namespace osu.Game.Screens.SelectV2 public Framework.Graphics.UserInterface.Popover GetPopover() => new Popover(this, beatmap.Value) { ColourProvider = colourProvider, - BeatmapActions = beatmapActions + SongSelect = songSelect }; } } diff --git a/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs b/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs index ca43bc3fe5..9dc50b87d4 100644 --- a/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs +++ b/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.SelectV2 // Can't use DI for these due to popover being initialised from a footer button which ends up being on the global // PopoverContainer. - public ISongSelectBeatmapActions? BeatmapActions { get; init; } + public ISongSelect? SongSelect { get; init; } public required OverlayColourProvider ColourProvider { get; init; } public Popover(FooterButtonOptions footerButton, WorkingBeatmap beatmap) @@ -59,20 +59,20 @@ namespace osu.Game.Screens.SelectV2 }; addHeader(CommonStrings.General); - addButton(SongSelectStrings.ManageCollections, FontAwesome.Solid.Book, () => BeatmapActions?.ManageCollections()); + addButton(SongSelectStrings.ManageCollections, FontAwesome.Solid.Book, () => SongSelect?.ManageCollections()); addHeader(SongSelectStrings.ForAllDifficulties, beatmap.BeatmapSetInfo.ToString()); - addButton(SongSelectStrings.DeleteBeatmap, FontAwesome.Solid.Trash, () => BeatmapActions?.Delete(beatmap.BeatmapSetInfo), colours.Red1); + addButton(SongSelectStrings.DeleteBeatmap, FontAwesome.Solid.Trash, () => SongSelect?.Delete(beatmap.BeatmapSetInfo), colours.Red1); addHeader(SongSelectStrings.ForSelectedDifficulty, beatmap.BeatmapInfo.DifficultyName); // TODO: replace with "remove from played" button when beatmap is already played. - addButton(SongSelectStrings.MarkAsPlayed, FontAwesome.Regular.TimesCircle, () => BeatmapActions?.MarkPlayed(beatmap.BeatmapInfo)); - addButton(SongSelectStrings.ClearAllLocalScores, FontAwesome.Solid.Eraser, () => BeatmapActions?.ClearScores(beatmap.BeatmapInfo), colours.Red1); + addButton(SongSelectStrings.MarkAsPlayed, FontAwesome.Regular.TimesCircle, () => SongSelect?.MarkPlayed(beatmap.BeatmapInfo)); + addButton(SongSelectStrings.ClearAllLocalScores, FontAwesome.Solid.Eraser, () => SongSelect?.ClearScores(beatmap.BeatmapInfo), colours.Red1); - if (BeatmapActions?.EditingAllowed == true) - addButton(SongSelectStrings.EditBeatmap, FontAwesome.Solid.PencilAlt, () => BeatmapActions.Edit(beatmap.BeatmapInfo)); + if (SongSelect?.EditingAllowed == true) + addButton(SongSelectStrings.EditBeatmap, FontAwesome.Solid.PencilAlt, () => SongSelect.Edit(beatmap.BeatmapInfo)); - addButton(WebCommonStrings.ButtonsHide.ToSentence(), FontAwesome.Solid.Magic, () => BeatmapActions?.Hide(beatmap.BeatmapInfo)); + addButton(WebCommonStrings.ButtonsHide.ToSentence(), FontAwesome.Solid.Magic, () => SongSelect?.Hide(beatmap.BeatmapInfo)); } protected override void LoadComplete() diff --git a/osu.Game/Screens/SelectV2/ISongSelectBeatmapActions.cs b/osu.Game/Screens/SelectV2/ISongSelect.cs similarity index 87% rename from osu.Game/Screens/SelectV2/ISongSelectBeatmapActions.cs rename to osu.Game/Screens/SelectV2/ISongSelect.cs index 388967bc4f..6c5954d82e 100644 --- a/osu.Game/Screens/SelectV2/ISongSelectBeatmapActions.cs +++ b/osu.Game/Screens/SelectV2/ISongSelect.cs @@ -2,13 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Scoring; namespace osu.Game.Screens.SelectV2 { /// /// Actions exposed by song select which are used by subcomponents to perform top-level operations. /// - public interface ISongSelectBeatmapActions + public interface ISongSelect { /// /// Requests the user for confirmation to delete the given beatmap set. @@ -44,5 +45,10 @@ namespace osu.Game.Screens.SelectV2 /// Hides a beatmap from user's vision. /// void Hide(BeatmapInfo beatmap); + + /// + /// Present the provided score at the results screen. + /// + void PresentScore(ScoreInfo score); } } diff --git a/osu.Game/Screens/SelectV2/SoloSongSelect.cs b/osu.Game/Screens/SelectV2/SoloSongSelect.cs index 14180fc695..7d62af8c9c 100644 --- a/osu.Game/Screens/SelectV2/SoloSongSelect.cs +++ b/osu.Game/Screens/SelectV2/SoloSongSelect.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Screens; @@ -11,9 +10,7 @@ using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking; using osu.Game.Utils; namespace osu.Game.Screens.SelectV2 @@ -28,18 +25,6 @@ namespace osu.Game.Screens.SelectV2 public override bool EditingAllowed => true; - /// - /// Opens results screen with the given score. - /// This assumes active beatmap and ruleset selection matches the score. - /// - public void PresentScore(ScoreInfo score) - { - Debug.Assert(Beatmap.Value.BeatmapInfo.Equals(score.BeatmapInfo)); - Debug.Assert(Ruleset.Value.Equals(score.Ruleset)); - - this.Push(new SoloResultsScreen(score)); - } - protected override bool OnStart() { if (playerLoader != null) return false; diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 43fa394e39..1ab429b16e 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -21,9 +22,11 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Volume; +using osu.Game.Scoring; using osu.Game.Screens.Edit; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; +using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Skinning; using osu.Game.Utils; @@ -37,8 +40,8 @@ namespace osu.Game.Screens.SelectV2 /// This screen is intended to house all components introduced in the new song select design to add transitions and examine the overall look. /// This will be gradually built upon and ultimately replace once everything is in place. /// - [Cached(typeof(ISongSelectBeatmapActions))] - public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler, ISongSelectBeatmapActions + [Cached(typeof(ISongSelect))] + public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler, ISongSelect { private const float logo_scale = 0.4f; private const double fade_duration = 300; @@ -395,6 +398,18 @@ namespace osu.Game.Screens.SelectV2 #endregion + /// + /// Opens results screen with the given score. + /// This assumes active beatmap and ruleset selection matches the score. + /// + public void PresentScore(ScoreInfo score) + { + Debug.Assert(Beatmap.Value.BeatmapInfo.Equals(score.BeatmapInfo)); + Debug.Assert(Ruleset.Value.Equals(score.Ruleset)); + + this.Push(new SoloResultsScreen(score)); + } + #region Beatmap management public virtual bool EditingAllowed => false; From c642d06576b179010b9ea59582b3e9212f41fa46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 May 2025 18:35:38 +0900 Subject: [PATCH 3/4] Add test coverage of opening score from song select v2 --- .../SongSelectV2/SongSelectTestScene.cs | 8 +++ .../SongSelectV2/TestSceneSongSelect.cs | 56 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs index 2f3aa9dc0f..4ca6c5a549 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectTestScene.cs @@ -15,10 +15,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; @@ -33,6 +35,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 protected BeatmapManager Beatmaps { get; private set; } = null!; protected RealmRulesetStore Rulesets { get; private set; } = null!; protected OsuConfigManager Config { get; private set; } = null!; + protected ScoreManager ScoreManager { get; private set; } = null!; private RealmDetachedBeatmapStore beatmapStore = null!; @@ -51,6 +54,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Cached(typeof(INotificationOverlay))] private readonly INotificationOverlay notificationOverlay = new NotificationOverlay(); + [Cached] + protected readonly LeaderboardManager LeaderboardManager = new LeaderboardManager(); + protected SongSelectTestScene() { Children = new Drawable[] @@ -60,6 +66,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + LeaderboardManager, new Toolbar { State = { Value = Visibility.Visible }, @@ -90,6 +97,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 dependencies.Cache(Realm); dependencies.Cache(Beatmaps = new BeatmapManager(LocalStorage, Realm, null, Dependencies.Get(), Resources, Dependencies.Get(), Beatmap.Default)); dependencies.Cache(Config = new OsuConfigManager(LocalStorage)); + dependencies.Cache(ScoreManager = new ScoreManager(Rulesets, () => Beatmaps, LocalStorage, Realm, API, Config)); dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index 1bf9fecbb8..76b74f2095 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -8,12 +8,19 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Screens.SelectV2; using osuTK.Input; using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods; using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions; @@ -22,6 +29,55 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneSongSelect : SongSelectTestScene { + [Test] + public void TestResultsScreenWhenClickingLeaderboardScore() + { + LoadSongSelect(); + 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($"import score", () => + { + var beatmapInfo = Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First(); + ScoreManager.Import(new ScoreInfo + { + Hash = Guid.NewGuid().ToString(), + BeatmapHash = beatmapInfo.Hash, + BeatmapInfo = beatmapInfo, + Ruleset = new OsuRuleset().RulesetInfo, + User = new GuestUser(), + }); + }); + + AddStep("select ranking tab", () => + { + InputManager.MoveMouseTo(SongSelect.ChildrenOfType>().Last()); + InputManager.Click(MouseButton.Left); + }); + + // probably should be done via dropdown menu instead of forcing this way? + AddStep("set local scope", () => + { + var current = LeaderboardManager.CurrentCriteria!; + LeaderboardManager.FetchWithCriteria(new LeaderboardCriteria(current.Beatmap, current.Ruleset, BeatmapLeaderboardScope.Local, null)); + }); + + AddUntilStep("wait for score panel", () => SongSelect.ChildrenOfType().Any()); + AddStep("click score panel", () => + { + InputManager.MoveMouseTo(SongSelect.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for results screen", () => Stack.CurrentScreen is ResultsScreen); + } + #region Hotkeys [Test] From 4992e4e5e1d7d452e8e1b672e1a0f4854bfe0597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 May 2025 13:23:05 +0200 Subject: [PATCH 4/4] Fix code quality --- osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index 76b74f2095..3161e62683 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First())); AddAssert("beatmap selected", () => !Beatmap.IsDefault); - AddStep($"import score", () => + AddStep("import score", () => { var beatmapInfo = Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First(); ScoreManager.Import(new ScoreInfo