1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 07:32:55 +08:00

Merge pull request #1205 from peppy/song-select-context-menus

Add support for hiding individual beatmap difficulties
This commit is contained in:
Dan Balasescu 2017-09-04 08:10:43 +09:00 committed by GitHub
commit 9a4cff8813
12 changed files with 223 additions and 52 deletions

@ -1 +1 @@
Subproject commit 167d5cda8f3ddae702ffc8d8d22dac67e48b509c Subproject commit 2bd341b29d6a7ed864aa9c1c5fad4668dafe03a4

View File

@ -52,6 +52,8 @@ namespace osu.Game.Beatmaps
[JsonProperty("file_sha2")] [JsonProperty("file_sha2")]
public string Hash { get; set; } public string Hash { get; set; }
public bool Hidden { get; set; }
/// <summary> /// <summary>
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
/// </summary> /// </summary>

View File

@ -33,11 +33,21 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public event Action<BeatmapSetInfo> BeatmapSetAdded; public event Action<BeatmapSetInfo> BeatmapSetAdded;
/// <summary>
/// Fired when a single difficulty has been hidden.
/// </summary>
public event Action<BeatmapInfo> BeatmapHidden;
/// <summary> /// <summary>
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database. /// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
/// </summary> /// </summary>
public event Action<BeatmapSetInfo> BeatmapSetRemoved; public event Action<BeatmapSetInfo> BeatmapSetRemoved;
/// <summary>
/// Fired when a single difficulty has been restored.
/// </summary>
public event Action<BeatmapInfo> BeatmapRestored;
/// <summary> /// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available. /// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary> /// </summary>
@ -71,6 +81,8 @@ namespace osu.Game.Beatmaps
beatmaps = new BeatmapStore(connection); beatmaps = new BeatmapStore(connection);
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
this.storage = storage; this.storage = storage;
this.files = files; this.files = files;
@ -162,7 +174,6 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database. // If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return; if (beatmapSetInfo.ID != 0) return;
lock (beatmaps)
beatmaps.Add(beatmapSetInfo); beatmaps.Add(beatmapSetInfo);
} }
@ -170,16 +181,27 @@ namespace osu.Game.Beatmaps
/// Delete a beatmap from the manager. /// Delete a beatmap from the manager.
/// Is a no-op for already deleted beatmaps. /// Is a no-op for already deleted beatmaps.
/// </summary> /// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param> /// <param name="beatmapSet">The beatmap set to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet) public void Delete(BeatmapSetInfo beatmapSet)
{ {
lock (beatmaps)
if (!beatmaps.Delete(beatmapSet)) return; if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected) if (!beatmapSet.Protected)
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
} }
/// <summary>
/// Delete a beatmap difficulty.
/// </summary>
/// <param name="beatmap">The beatmap difficulty to hide.</param>
public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap);
/// <summary>
/// Restore a beatmap difficulty.
/// </summary>
/// <param name="beatmap">The beatmap difficulty to restore.</param>
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
/// <summary> /// <summary>
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged. /// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged.
/// Is a no-op for already usable beatmaps. /// Is a no-op for already usable beatmaps.
@ -187,7 +209,6 @@ namespace osu.Game.Beatmaps
/// <param name="beatmapSet">The beatmap to restore.</param> /// <param name="beatmapSet">The beatmap to restore.</param>
public void Undelete(BeatmapSetInfo beatmapSet) public void Undelete(BeatmapSetInfo beatmapSet)
{ {
lock (beatmaps)
if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmaps.Undelete(beatmapSet)) return;
if (!beatmapSet.Protected) if (!beatmapSet.Protected)
@ -248,6 +269,13 @@ namespace osu.Game.Beatmaps
} }
} }
/// <summary>
/// Refresh an existing instance of a <see cref="BeatmapSetInfo"/> from the store.
/// </summary>
/// <param name="beatmapSet">A stale instance.</param>
/// <returns>A fresh instance.</returns>
public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
/// <summary> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary> /// </summary>
@ -255,7 +283,7 @@ namespace osu.Game.Beatmaps
/// <returns>Results from the provided query.</returns> /// <returns>Results from the provided query.</returns>
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query) public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query)
{ {
lock (beatmaps) return beatmaps.QueryAndPopulate(query); return beatmaps.QueryAndPopulate(query);
} }
/// <summary> /// <summary>
@ -264,8 +292,6 @@ namespace osu.Game.Beatmaps
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns> /// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query) public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
{
lock (beatmaps)
{ {
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query); BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
@ -274,7 +300,6 @@ namespace osu.Game.Beatmaps
return set; return set;
} }
}
/// <summary> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapInfo"/>s.

View File

@ -16,11 +16,14 @@ namespace osu.Game.Beatmaps
public event Action<BeatmapSetInfo> BeatmapSetAdded; public event Action<BeatmapSetInfo> BeatmapSetAdded;
public event Action<BeatmapSetInfo> BeatmapSetRemoved; public event Action<BeatmapSetInfo> BeatmapSetRemoved;
public event Action<BeatmapInfo> BeatmapHidden;
public event Action<BeatmapInfo> BeatmapRestored;
/// <summary> /// <summary>
/// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>). /// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>).
/// The initial version is 1. /// The initial version is 1.
/// </summary> /// </summary>
protected override int StoreVersion => 3; protected override int StoreVersion => 4;
public BeatmapStore(SQLiteConnection connection) public BeatmapStore(SQLiteConnection connection)
: base(connection) : base(connection)
@ -81,6 +84,10 @@ namespace osu.Game.Beatmaps
// Added MD5Hash column to BeatmapInfo // Added MD5Hash column to BeatmapInfo
Connection.MigrateTable<BeatmapInfo>(); Connection.MigrateTable<BeatmapInfo>();
break; break;
case 4:
// Added Hidden column to BeatmapInfo
Connection.MigrateTable<BeatmapInfo>();
break;
} }
} }
} }
@ -100,7 +107,7 @@ namespace osu.Game.Beatmaps
} }
/// <summary> /// <summary>
/// Delete a <see cref="BeatmapSetInfo"/> to the database. /// Delete a <see cref="BeatmapSetInfo"/> from the database.
/// </summary> /// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param> /// <param name="beatmapSet">The beatmap to delete.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns> /// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
@ -131,6 +138,38 @@ namespace osu.Game.Beatmaps
return true; return true;
} }
/// <summary>
/// Hide a <see cref="BeatmapInfo"/> in the database.
/// </summary>
/// <param name="beatmap">The beatmap to hide.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
public bool Hide(BeatmapInfo beatmap)
{
if (beatmap.Hidden) return false;
beatmap.Hidden = true;
Connection.Update(beatmap);
BeatmapHidden?.Invoke(beatmap);
return true;
}
/// <summary>
/// Restore a previously hidden <see cref="BeatmapInfo"/>.
/// </summary>
/// <param name="beatmap">The beatmap to restore.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
public bool Restore(BeatmapInfo beatmap)
{
if (!beatmap.Hidden) return false;
beatmap.Hidden = false;
Connection.Update(beatmap);
BeatmapRestored?.Invoke(beatmap);
return true;
}
private void cleanupPendingDeletions() private void cleanupPendingDeletions()
{ {
Connection.RunInTransaction(() => Connection.RunInTransaction(() =>

View File

@ -23,6 +23,12 @@ namespace osu.Game.Beatmaps.Drawables
/// </summary> /// </summary>
public Action<BeatmapInfo> StartRequested; public Action<BeatmapInfo> StartRequested;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
public Action<BeatmapInfo> HideDifficultyRequested;
public BeatmapSetHeader Header; public BeatmapSetHeader Header;
private BeatmapGroupState state; private BeatmapGroupState state;
@ -66,14 +72,17 @@ namespace osu.Game.Beatmaps.Drawables
Header = new BeatmapSetHeader(beatmap) Header = new BeatmapSetHeader(beatmap)
{ {
GainedSelection = headerGainedSelection, GainedSelection = headerGainedSelection,
DeleteRequested = b => DeleteRequested(b),
RestoreHiddenRequested = b => RestoreHiddenRequested(b),
RelativeSizeAxes = Axes.X, 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) BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
{ {
Alpha = 0, Alpha = 0,
GainedSelection = panelGainedSelection, GainedSelection = panelGainedSelection,
HideRequested = p => HideDifficultyRequested?.Invoke(p),
StartRequested = p => { StartRequested?.Invoke(p.Beatmap); }, StartRequested = p => { StartRequested?.Invoke(p.Beatmap); },
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}).ToList(); }).ToList();

View File

@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
@ -14,16 +15,20 @@ using OpenTK.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
public class BeatmapPanel : Panel public class BeatmapPanel : Panel, IHasContextMenu
{ {
public BeatmapInfo Beatmap; public BeatmapInfo Beatmap;
private readonly Sprite background; private readonly Sprite background;
public Action<BeatmapPanel> GainedSelection; public Action<BeatmapPanel> GainedSelection;
public Action<BeatmapPanel> StartRequested; public Action<BeatmapPanel> StartRequested;
public Action<BeatmapPanel> EditRequested;
public Action<BeatmapInfo> HideRequested;
private readonly Triangles triangles; private readonly Triangles triangles;
private readonly StarCounter starCounter; private readonly StarCounter starCounter;
@ -148,5 +153,12 @@ namespace osu.Game.Beatmaps.Drawables
} }
}; };
} }
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)),
new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)),
new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(Beatmap)),
};
} }
} }

View File

@ -3,22 +3,31 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
public class BeatmapSetHeader : Panel public class BeatmapSetHeader : Panel, IHasContextMenu
{ {
public Action<BeatmapSetHeader> GainedSelection; public Action<BeatmapSetHeader> GainedSelection;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
private readonly SpriteText title; private readonly SpriteText title;
private readonly SpriteText artist; private readonly SpriteText artist;
@ -148,5 +157,23 @@ namespace osu.Game.Beatmaps.Drawables
foreach (var p in panels) foreach (var p in panels)
difficultyIcons.Add(new DifficultyIcon(p.Beatmap)); difficultyIcons.Add(new DifficultyIcon(p.Beatmap));
} }
public MenuItem[] ContextMenuItems
{
get
{
List<MenuItem> items = new List<MenuItem>();
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();
}
}
} }
} }

View File

@ -13,6 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
private OsuButton importButton; private OsuButton importButton;
private OsuButton deleteButton; private OsuButton deleteButton;
private OsuButton restoreButton;
protected override string Header => "General"; protected override string Header => "General";
@ -41,6 +42,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
} }
}, },
restoreButton = new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = "Restore all hidden difficulties",
Action = () =>
{
restoreButton.Enabled.Value = false;
Task.Run(() =>
{
foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden))
beatmaps.Restore(b);
}).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true));
}
},
}; };
} }
} }

View File

@ -107,17 +107,44 @@ 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) public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true)
{ {
if (beatmap == null) if (beatmap == null || beatmap.Hidden)
{ {
SelectNext(); SelectNext();
return; return;
@ -140,6 +167,12 @@ namespace osu.Game.Screens.Select
public Action StartRequested; public Action StartRequested;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreRequested;
public Action<BeatmapInfo> HideDifficultyRequested;
public void SelectNext(int direction = 1, bool skipDifficulties = true) public void SelectNext(int direction = 1, bool skipDifficulties = true)
{ {
if (groups.All(g => g.State == BeatmapGroupState.Hidden)) if (groups.All(g => g.State == BeatmapGroupState.Hidden))
@ -306,6 +339,9 @@ namespace osu.Game.Screens.Select
{ {
SelectionChanged = (g, p) => selectGroup(g, p), SelectionChanged = (g, p) => selectGroup(g, p),
StartRequested = b => StartRequested?.Invoke(), StartRequested = b => StartRequested?.Invoke(),
DeleteRequested = b => DeleteRequested?.Invoke(b),
RestoreHiddenRequested = s => RestoreRequested?.Invoke(s),
HideDifficultyRequested = b => HideDifficultyRequested?.Invoke(b),
State = BeatmapGroupState.Collapsed State = BeatmapGroupState.Collapsed
}; };
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -19,23 +18,18 @@ namespace osu.Game.Screens.Select
manager = beatmapManager; manager = beatmapManager;
} }
public BeatmapDeleteDialog(WorkingBeatmap beatmap) public BeatmapDeleteDialog(BeatmapSetInfo beatmap)
{ {
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
Icon = FontAwesome.fa_trash_o; Icon = FontAwesome.fa_trash_o;
HeaderText = @"Confirm deletion of"; HeaderText = @"Confirm deletion of";
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
Buttons = new PopupDialogButton[] Buttons = new PopupDialogButton[]
{ {
new PopupDialogOkButton new PopupDialogOkButton
{ {
Text = @"Yes. Totally. Delete it.", Text = @"Yes. Totally. Delete it.",
Action = () => Action = () => manager.Delete(beatmap),
{
beatmap.Dispose();
manager.Delete(beatmap.BeatmapSetInfo);
},
}, },
new PopupDialogCancelButton new PopupDialogCancelButton
{ {

View File

@ -54,13 +54,11 @@ namespace osu.Game.Screens.Select
ValidForResume = false; ValidForResume = false;
Push(new Editor()); Push(new Editor());
}, Key.Number3); }, Key.Number3);
Beatmap.ValueChanged += beatmap_ValueChanged;
} }
private void beatmap_ValueChanged(WorkingBeatmap beatmap) protected override void UpdateBeatmap(WorkingBeatmap beatmap)
{ {
if (!IsCurrentScreen) return; base.UpdateBeatmap(beatmap);
beatmap.Mods.BindTo(modSelect.SelectedMods); beatmap.Mods.BindTo(modSelect.SelectedMods);

View File

@ -106,6 +106,9 @@ namespace osu.Game.Screens.Select
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
SelectionChanged = carouselSelectionChanged, SelectionChanged = carouselSelectionChanged,
BeatmapsChanged = carouselBeatmapsLoaded, BeatmapsChanged = carouselBeatmapsLoaded,
DeleteRequested = b => promptDelete(b),
RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); },
HideDifficultyRequested = b => manager.Hide(b),
StartRequested = () => carouselRaisedStart(), StartRequested = () => carouselRaisedStart(),
}); });
Add(FilterControl = new FilterControl Add(FilterControl = new FilterControl
@ -163,7 +166,7 @@ namespace osu.Game.Screens.Select
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2);
Footer.AddButton(@"options", colours.Blue, BeatmapOptions.ToggleVisibility, Key.F3); Footer.AddButton(@"options", colours.Blue, BeatmapOptions.ToggleVisibility, Key.F3);
BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, promptDelete, Key.Number4, float.MaxValue); BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => promptDelete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
} }
if (manager == null) if (manager == null)
@ -174,6 +177,8 @@ namespace osu.Game.Screens.Select
manager.BeatmapSetAdded += onBeatmapSetAdded; manager.BeatmapSetAdded += onBeatmapSetAdded;
manager.BeatmapSetRemoved += onBeatmapSetRemoved; manager.BeatmapSetRemoved += onBeatmapSetRemoved;
manager.BeatmapHidden += onBeatmapHidden;
manager.BeatmapRestored += onBeatmapRestored;
dialogOverlay = dialog; dialogOverlay = dialog;
@ -190,6 +195,9 @@ namespace osu.Game.Screens.Select
carousel.AllowSelection = !Beatmap.Disabled; carousel.AllowSelection = !Beatmap.Disabled;
} }
private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmap(b);
private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmap(b);
private void carouselBeatmapsLoaded() private void carouselBeatmapsLoaded()
{ {
if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false)
@ -236,7 +244,7 @@ namespace osu.Game.Screens.Select
ensurePlayingSelected(preview); ensurePlayingSelected(preview);
} }
changeBackground(Beatmap.Value); UpdateBeatmap(Beatmap.Value);
}; };
selectionChangedDebounce?.Cancel(); selectionChangedDebounce?.Cancel();
@ -248,6 +256,7 @@ namespace osu.Game.Screens.Select
if (beatmap == null) if (beatmap == null)
{ {
if (!Beatmap.IsDefault)
performLoad(); performLoad();
} }
else else
@ -303,7 +312,7 @@ namespace osu.Game.Screens.Select
{ {
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
{ {
changeBackground(Beatmap); UpdateBeatmap(Beatmap);
ensurePlayingSelected(); ensurePlayingSelected();
} }
@ -349,7 +358,12 @@ namespace osu.Game.Screens.Select
initialAddSetsTask?.Cancel(); initialAddSetsTask?.Cancel();
} }
private void changeBackground(WorkingBeatmap beatmap) /// <summary>
/// Allow components in SongSelect to update their loaded beatmap details.
/// This is a debounced call (unlike directly binding to WorkingBeatmap.ValueChanged).
/// </summary>
/// <param name="beatmap">The working beatmap.</param>
protected virtual void UpdateBeatmap(WorkingBeatmap beatmap)
{ {
var backgroundModeBeatmap = Background as BackgroundScreenBeatmap; var backgroundModeBeatmap = Background as BackgroundScreenBeatmap;
if (backgroundModeBeatmap != null) if (backgroundModeBeatmap != null)
@ -377,10 +391,7 @@ namespace osu.Game.Screens.Select
} }
} }
private void addBeatmapSet(BeatmapSetInfo beatmapSet) private void addBeatmapSet(BeatmapSetInfo beatmapSet) => carousel.AddBeatmap(beatmapSet);
{
carousel.AddBeatmap(beatmapSet);
}
private void removeBeatmapSet(BeatmapSetInfo beatmapSet) private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
{ {
@ -389,10 +400,12 @@ namespace osu.Game.Screens.Select
Beatmap.SetDefault(); Beatmap.SetDefault();
} }
private void promptDelete() private void promptDelete(BeatmapSetInfo beatmap)
{ {
if (Beatmap != null && !Beatmap.IsDefault) if (beatmap == null)
dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap)); return;
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@ -408,7 +421,8 @@ namespace osu.Game.Screens.Select
case Key.Delete: case Key.Delete:
if (state.Keyboard.ShiftPressed) if (state.Keyboard.ShiftPressed)
{ {
promptDelete(); if (!Beatmap.IsDefault)
promptDelete(Beatmap.Value.BeatmapSetInfo);
return true; return true;
} }
break; break;