From 2bbd1ee38463c4ea32138f5b309d955e07bb2f93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 May 2025 15:04:41 +0900 Subject: [PATCH] SongSelectV2: Add back ability to manage collections from beatmap / set panel context menus --- .../SelectV2/FooterButtonOptions_Popover.cs | 4 ++ osu.Game/Screens/SelectV2/PanelBeatmapSet.cs | 65 +++++++++++++++++++ osu.Game/Screens/SelectV2/SoloSongSelect.cs | 3 + osu.Game/Screens/SelectV2/SongSelect.cs | 24 +++++++ 4 files changed, 96 insertions(+) diff --git a/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs b/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs index 3031dcb8f7..039020d7c4 100644 --- a/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs +++ b/osu.Game/Screens/SelectV2/FooterButtonOptions_Popover.cs @@ -68,6 +68,10 @@ namespace osu.Game.Screens.SelectV2 foreach (OsuMenuItem item in SongSelect.GetForwardActions(beatmap.BeatmapInfo)) { + // We can't display menus with child items here, so just ignore them. + if (item.Items.Any()) + continue; + if (item is OsuMenuItemSpacer) { buttonFlow.Add(new Container diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index a41c5c75ae..425ca02e5a 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -14,6 +14,8 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Sprites; @@ -188,6 +190,12 @@ namespace osu.Game.Screens.SelectV2 difficultiesDisplay.BeatmapSet = null; } + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + public override MenuItem[] ContextMenuItems { get @@ -215,6 +223,17 @@ namespace osu.Game.Screens.SelectV2 items.Add(new OsuMenuItemSpacer()); } + var collectionItems = realm.Realm.All() + .OrderBy(c => c.Name) + .AsEnumerable() + .Select(createCollectionMenuItem) + .ToList(); + + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => songSelect?.RestoreAllHidden(beatmapSet))); @@ -222,5 +241,51 @@ namespace osu.Game.Screens.SelectV2 return items.ToArray(); } } + + private MenuItem createCollectionMenuItem(BeatmapCollection collection) + { + var beatmapSet = (BeatmapSetInfo)Item!.Model; + + Debug.Assert(beatmapSet != null); + + TernaryState state; + + int countExisting = beatmapSet.Beatmaps.Count(b => collection.BeatmapMD5Hashes.Contains(b.MD5Hash)); + + if (countExisting == beatmapSet.Beatmaps.Count) + state = TernaryState.True; + else if (countExisting > 0) + state = TernaryState.Indeterminate; + else + state = TernaryState.False; + + var liveCollection = collection.ToLive(realm); + + return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s => + { + liveCollection.PerformWrite(c => + { + foreach (var b in beatmapSet.Beatmaps) + { + switch (s) + { + case TernaryState.True: + if (c.BeatmapMD5Hashes.Contains(b.MD5Hash)) + continue; + + c.BeatmapMD5Hashes.Add(b.MD5Hash); + break; + + case TernaryState.False: + c.BeatmapMD5Hashes.Remove(b.MD5Hash); + break; + } + } + }); + }) + { + State = { Value = state } + }; + } } } diff --git a/osu.Game/Screens/SelectV2/SoloSongSelect.cs b/osu.Game/Screens/SelectV2/SoloSongSelect.cs index ea27ffef37..a136e682c4 100644 --- a/osu.Game/Screens/SelectV2/SoloSongSelect.cs +++ b/osu.Game/Screens/SelectV2/SoloSongSelect.cs @@ -73,6 +73,9 @@ namespace osu.Game.Screens.SelectV2 yield return new OsuMenuItemSpacer(); } + foreach (var i in CreateCollectionMenuActions(beatmap)) + yield return i; + // TODO: replace with "remove from played" button when beatmap is already played. yield return new OsuMenuItem(SongSelectStrings.MarkAsPlayed, MenuItemType.Standard, () => beatmaps.MarkPlayed(beatmap)) { Icon = FontAwesome.Solid.TimesCircle }; yield return new OsuMenuItem(SongSelectStrings.ClearAllLocalScores, MenuItemType.Standard, () => dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap))) diff --git a/osu.Game/Screens/SelectV2/SongSelect.cs b/osu.Game/Screens/SelectV2/SongSelect.cs index 1056c2ff71..6feaeb0ef2 100644 --- a/osu.Game/Screens/SelectV2/SongSelect.cs +++ b/osu.Game/Screens/SelectV2/SongSelect.cs @@ -21,6 +21,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics.Carousel; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -659,6 +660,12 @@ namespace osu.Game.Screens.SelectV2 #region Beatmap management + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } = null!; + public virtual IEnumerable GetForwardActions(BeatmapInfo beatmap) { yield return new OsuMenuItem("Select", MenuItemType.Highlighted, () => SelectAndStart(beatmap)) @@ -675,6 +682,23 @@ namespace osu.Game.Screens.SelectV2 if (beatmap.GetOnlineURL(api, Ruleset.Value) is string url) yield return new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => (game as OsuGame)?.CopyToClipboard(url)); } + + yield return new OsuMenuItemSpacer(); + + foreach (var i in CreateCollectionMenuActions(beatmap)) + yield return i; + } + + protected IEnumerable CreateCollectionMenuActions(BeatmapInfo beatmap) + { + var collectionItems = realm.Realm.All() + .OrderBy(c => c.Name) + .AsEnumerable() + .Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); + + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, () => manageCollectionsDialog?.Show())); + + yield return new OsuMenuItem("Collections") { Items = collectionItems }; } public void ManageCollections() => collectionsDialog?.Show();