From 1f646e6d548a988fba0fbc102a09e02574a35c85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Aug 2017 15:49:56 +0900 Subject: [PATCH] Add hiding support for beatmap difficulties --- osu-framework | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 2 + osu.Game/Beatmaps/BeatmapManager.cs | 57 ++++++++++++------- osu.Game/Beatmaps/BeatmapStore.cs | 43 +++++++++++++- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 5 +- osu.Game/Beatmaps/Drawables/BeatmapPanel.cs | 2 +- .../Beatmaps/Drawables/BeatmapSetHeader.cs | 6 ++ osu.Game/Screens/Select/BeatmapCarousel.cs | 38 +++++++++++-- .../Screens/Select/BeatmapDeleteDialog.cs | 19 +------ osu.Game/Screens/Select/SongSelect.cs | 21 +++---- 10 files changed, 136 insertions(+), 59 deletions(-) diff --git a/osu-framework b/osu-framework index 167d5cda8f..2804e052ca 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 167d5cda8f3ddae702ffc8d8d22dac67e48b509c +Subproject commit 2804e052ca2ce068f015771d28170d18573687e1 diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index ebf77bf9df..c962201fe3 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -52,6 +52,8 @@ namespace osu.Game.Beatmaps [JsonProperty("file_sha2")] public string Hash { get; set; } + public bool Hidden { get; set; } + /// /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). /// diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c52576fb9f..551612330b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -33,11 +33,21 @@ namespace osu.Game.Beatmaps /// public event Action BeatmapSetAdded; + /// + /// Fired when a single difficulty has been hidden. + /// + public event Action BeatmapHidden; + /// /// Fired when a is removed from the database. /// public event Action BeatmapSetRemoved; + /// + /// Fired when a single difficulty has been restored. + /// + public event Action BeatmapRestored; + /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. /// @@ -71,6 +81,8 @@ namespace osu.Game.Beatmaps beatmaps = new BeatmapStore(connection); beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); + beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); + beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); this.storage = storage; this.files = files; @@ -162,8 +174,7 @@ namespace osu.Game.Beatmaps // If we have an ID then we already exist in the database. if (beatmapSetInfo.ID != 0) return; - lock (beatmaps) - beatmaps.Add(beatmapSetInfo); + beatmaps.Add(beatmapSetInfo); } /// @@ -173,22 +184,23 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - lock (beatmaps) - if (!beatmaps.Delete(beatmapSet)) return; + if (!beatmaps.Delete(beatmapSet)) return; if (!beatmapSet.Protected) files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); } /// - /// Delete a beatmap from the manager. - /// Is a no-op for already deleted beatmaps. + /// Delete a beatmap difficulty. /// - /// The beatmap difficulty to delete. - public void Delete(BeatmapInfo beatmap) - { - //todo: implement - } + /// The beatmap difficulty to hide. + public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); + + /// + /// Restore a beatmap difficulty. + /// + /// The beatmap difficulty to restore. + public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); /// /// Returns a to a usable state if it has previously been deleted but not yet purged. @@ -197,8 +209,7 @@ namespace osu.Game.Beatmaps /// The beatmap to restore. public void Undelete(BeatmapSetInfo beatmapSet) { - lock (beatmaps) - if (!beatmaps.Undelete(beatmapSet)) return; + if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmapSet.Protected) files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -258,6 +269,13 @@ namespace osu.Game.Beatmaps } } + /// + /// Refresh an existing instance of a from the store. + /// + /// A stale instance. + /// A fresh instance. + public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID); + /// /// Perform a lookup query on available s. /// @@ -265,7 +283,7 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public List QueryBeatmapSets(Expression> query) { - lock (beatmaps) return beatmaps.QueryAndPopulate(query); + return beatmaps.QueryAndPopulate(query); } /// @@ -275,15 +293,12 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo QueryBeatmap(Func query) { - lock (beatmaps) - { - BeatmapInfo set = beatmaps.Query().FirstOrDefault(query); + BeatmapInfo set = beatmaps.Query().FirstOrDefault(query); - if (set != null) - beatmaps.Populate(set); + if (set != null) + beatmaps.Populate(set); - return set; - } + return set; } /// diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 8212712bf9..0f2d8cffa6 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -16,11 +16,14 @@ namespace osu.Game.Beatmaps public event Action BeatmapSetAdded; public event Action BeatmapSetRemoved; + public event Action BeatmapHidden; + public event Action BeatmapRestored; + /// /// The current version of this store. Used for migrations (see ). /// The initial version is 1. /// - protected override int StoreVersion => 3; + protected override int StoreVersion => 4; public BeatmapStore(SQLiteConnection connection) : base(connection) @@ -81,6 +84,10 @@ namespace osu.Game.Beatmaps // Added MD5Hash column to BeatmapInfo Connection.MigrateTable(); break; + case 4: + // Added Hidden column to BeatmapInfo + Connection.MigrateTable(); + break; } } } @@ -100,7 +107,7 @@ namespace osu.Game.Beatmaps } /// - /// Delete a to the database. + /// Delete a from the database. /// /// The beatmap to delete. /// Whether the beatmap's was changed. @@ -131,6 +138,38 @@ namespace osu.Game.Beatmaps return true; } + /// + /// Hide a in the database. + /// + /// The beatmap to hide. + /// Whether the beatmap's was changed. + public bool Hide(BeatmapInfo beatmap) + { + if (beatmap.Hidden) return false; + + beatmap.Hidden = true; + Connection.Update(beatmap); + + BeatmapHidden?.Invoke(beatmap); + return true; + } + + /// + /// Restore a previously hidden . + /// + /// The beatmap to restore. + /// Whether the beatmap's was changed. + public bool Restore(BeatmapInfo beatmap) + { + if (!beatmap.Hidden) return false; + + beatmap.Hidden = false; + Connection.Update(beatmap); + + BeatmapRestored?.Invoke(beatmap); + return true; + } + private void cleanupPendingDeletions() { Connection.RunInTransaction(() => diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index b47210620d..19dfc22506 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -25,6 +25,8 @@ namespace osu.Game.Beatmaps.Drawables public Action DeleteRequested; + public Action RestoreHiddenRequested; + public Action DeleteDifficultyRequested; public BeatmapSetHeader Header; @@ -71,10 +73,11 @@ namespace osu.Game.Beatmaps.Drawables { GainedSelection = headerGainedSelection, DeleteRequested = b => DeleteRequested(b), + RestoreHiddenRequested = b => RestoreHiddenRequested(b), RelativeSizeAxes = Axes.X, }; - BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList(); + BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList(); BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b) { Alpha = 0, diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index 1006380a2b..6b17b3bb8b 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -158,7 +158,7 @@ namespace osu.Game.Beatmaps.Drawables { new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)), new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)), - new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(Beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => DeleteRequested?.Invoke(Beatmap)), }; } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index b475a23ffd..ee75b77747 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -25,6 +26,8 @@ namespace osu.Game.Beatmaps.Drawables public Action DeleteRequested; + public Action RestoreHiddenRequested; + private readonly SpriteText title; private readonly SpriteText artist; @@ -164,6 +167,9 @@ namespace osu.Game.Beatmaps.Drawables if (State == PanelSelectedState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => State = PanelSelectedState.Selected)); + if (beatmap.BeatmapSetInfo.Beatmaps.Any(b => b.Hidden)) + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmap.BeatmapSetInfo))); + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmap.BeatmapSetInfo))); return items.ToArray(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index a1c7f64188..5d495a61f0 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -107,12 +107,39 @@ namespace osu.Game.Screens.Select }); } - public void RemoveBeatmap(BeatmapSetInfo beatmapSet) + public void RemoveBeatmap(BeatmapSetInfo beatmapSet) => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID)); + + internal void UpdateBeatmap(BeatmapInfo beatmap) { - Schedule(delegate + // todo: this method should not run more than once for the same BeatmapSetInfo. + var set = manager.Refresh(beatmap.BeatmapSet); + + // todo: this method should be smarter as to not recreate panels that haven't changed, etc. + var group = groups.Find(b => b.BeatmapSet.ID == set.ID); + + if (group == null) + return; + + var newGroup = createGroup(set); + + int i = groups.IndexOf(group); + groups.RemoveAt(i); + groups.Insert(i, newGroup); + + if (selectedGroup == group && newGroup.BeatmapPanels.Count == 0) + selectedGroup = null; + + Filter(null, false); + + //check if we can/need to maintain our current selection. + if (selectedGroup == group && newGroup.BeatmapPanels.Count > 0) { - removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID)); - }); + var newSelection = + newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID) ?? + newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, group.BeatmapPanels.IndexOf(selectedPanel))]; + + selectGroup(newGroup, newSelection); + } } public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true) @@ -142,6 +169,8 @@ namespace osu.Game.Screens.Select public Action DeleteRequested; + public Action RestoreRequested; + public Action DeleteDifficultyRequested; public void SelectNext(int direction = 1, bool skipDifficulties = true) @@ -310,6 +339,7 @@ namespace osu.Game.Screens.Select SelectionChanged = (g, p) => selectGroup(g, p), StartRequested = b => StartRequested?.Invoke(), DeleteRequested = b => DeleteRequested?.Invoke(b), + RestoreHiddenRequested = s => RestoreRequested?.Invoke(s), DeleteDifficultyRequested = b => DeleteDifficultyRequested?.Invoke(b), State = BeatmapGroupState.Collapsed }; diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index 15ec84a7d8..aa37705cdf 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -13,28 +12,16 @@ namespace osu.Game.Screens.Select { private BeatmapManager manager; - private readonly Action deleteAction; - [BackgroundDependencyLoader] private void load(BeatmapManager beatmapManager) { manager = beatmapManager; } - public BeatmapDeleteDialog(BeatmapSetInfo beatmap) : this() + public BeatmapDeleteDialog(BeatmapSetInfo beatmap) { - BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title} (ALL DIFFICULTIES)"; - deleteAction = () => manager.Delete(beatmap); - } + BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - public BeatmapDeleteDialog(BeatmapInfo beatmap) : this() - { - BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title} [{beatmap.Version}]"; - deleteAction = () => manager.Delete(beatmap); - } - - public BeatmapDeleteDialog() - { Icon = FontAwesome.fa_trash_o; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] @@ -42,7 +29,7 @@ namespace osu.Game.Screens.Select new PopupDialogOkButton { Text = @"Yes. Totally. Delete it.", - Action = () => deleteAction(), + Action = () => manager.Delete(beatmap), }, new PopupDialogCancelButton { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b3ee33e7c6..b743af5351 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -107,7 +107,8 @@ namespace osu.Game.Screens.Select SelectionChanged = carouselSelectionChanged, BeatmapsChanged = carouselBeatmapsLoaded, DeleteRequested = b => promptDelete(b), - DeleteDifficultyRequested = b => promptDelete(b), + RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); }, + DeleteDifficultyRequested = b => manager.Hide(b), StartRequested = () => carouselRaisedStart(), }); Add(FilterControl = new FilterControl @@ -176,6 +177,8 @@ namespace osu.Game.Screens.Select manager.BeatmapSetAdded += onBeatmapSetAdded; manager.BeatmapSetRemoved += onBeatmapSetRemoved; + manager.BeatmapHidden += onBeatmapHidden; + manager.BeatmapRestored += onBeatmapRestored; dialogOverlay = dialog; @@ -192,6 +195,9 @@ namespace osu.Game.Screens.Select carousel.AllowSelection = !Beatmap.Disabled; } + private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmap(b); + private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmap(b); + private void carouselBeatmapsLoaded() { if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) @@ -380,10 +386,7 @@ namespace osu.Game.Screens.Select } } - private void addBeatmapSet(BeatmapSetInfo beatmapSet) - { - carousel.AddBeatmap(beatmapSet); - } + private void addBeatmapSet(BeatmapSetInfo beatmapSet) => carousel.AddBeatmap(beatmapSet); private void removeBeatmapSet(BeatmapSetInfo beatmapSet) { @@ -400,14 +403,6 @@ namespace osu.Game.Screens.Select dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } - private void promptDelete(BeatmapInfo beatmap) - { - if (beatmap == null) - return; - - dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); - } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { if (args.Repeat) return false;