osu/osu.Game/Collections/CollectionDropdown.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

259 lines
9.2 KiB
C#
Raw Normal View History

2020-09-07 11:06:38 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
2020-09-07 11:06:38 +00:00
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
2020-09-07 11:06:38 +00:00
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
2020-09-08 04:45:26 +00:00
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
2020-09-07 11:06:38 +00:00
using osu.Game.Beatmaps;
using osu.Game.Database;
2020-09-07 11:06:38 +00:00
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osuTK;
2022-07-27 09:24:37 +00:00
using Realms;
2020-09-07 11:06:38 +00:00
2020-09-11 07:01:01 +00:00
namespace osu.Game.Collections
2020-09-07 11:06:38 +00:00
{
2020-09-07 12:08:48 +00:00
/// <summary>
2022-07-28 04:48:15 +00:00
/// A dropdown to select the collection to be used to filter results.
2020-09-07 12:08:48 +00:00
/// </summary>
2022-07-28 04:48:15 +00:00
public partial class CollectionDropdown : OsuDropdown<CollectionFilterMenuItem>
2020-09-07 11:06:38 +00:00
{
/// <summary>
/// Whether to show the "manage collections..." menu item in the dropdown.
/// </summary>
protected virtual bool ShowManageCollectionsItem => true;
public Action? RequestFilter { private get; set; }
2020-09-11 07:02:46 +00:00
private readonly BindableList<CollectionFilterMenuItem> filters = new BindableList<CollectionFilterMenuItem>();
2020-09-07 11:06:38 +00:00
[Resolved]
private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
2022-07-27 09:24:37 +00:00
[Resolved]
private RealmAccess realm { get; set; } = null!;
2022-07-28 04:48:15 +00:00
private IDisposable? realmSubscription;
public CollectionDropdown()
2020-09-07 11:06:38 +00:00
{
ItemSource = filters;
Current.Value = new AllBeatmapsCollectionFilterMenuItem();
2020-09-07 11:06:38 +00:00
}
protected override void LoadComplete()
2020-09-07 11:06:38 +00:00
{
base.LoadComplete();
2022-07-28 05:07:42 +00:00
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapCollection>().OrderBy(c => c.Name), collectionsChanged);
2020-09-09 07:33:48 +00:00
Current.BindValueChanged(selectionChanged);
2020-09-07 11:06:38 +00:00
}
2022-07-27 09:24:37 +00:00
private void collectionsChanged(IRealmCollection<BeatmapCollection> collections, ChangeSet? changes, Exception error)
2020-09-07 11:06:38 +00:00
{
var selectedItem = SelectedItem?.Value?.Collection;
var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem();
2020-09-07 11:06:38 +00:00
filters.Clear();
filters.Add(allBeatmaps);
2022-07-27 09:24:37 +00:00
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
if (ShowManageCollectionsItem)
filters.Add(new ManageCollectionsFilterMenuItem());
2020-09-07 11:06:38 +00:00
// This current update and schedule is required to work around dropdown headers not updating text even when the selected item
// changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue
// a warning that it's going to be a frustrating journey.
Current.Value = allBeatmaps;
Schedule(() =>
{
// current may have changed before the scheduled call is run.
if (Current.Value != allBeatmaps)
return;
Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0];
});
2022-07-27 09:24:37 +00:00
// Trigger a re-filter if the current item was in the change set.
2022-07-27 09:24:37 +00:00
if (selectedItem != null && changes != null)
{
foreach (int index in changes.ModifiedIndices)
{
if (collections[index].ID == selectedItem.ID)
RequestFilter?.Invoke();
2022-07-27 09:24:37 +00:00
}
}
2020-09-07 11:06:38 +00:00
}
private Live<BeatmapCollection>? lastFiltered;
private void selectionChanged(ValueChangedEvent<CollectionFilterMenuItem> filter)
2020-09-07 11:06:38 +00:00
{
// May be null during .Clear().
if (filter.NewValue.IsNull())
return;
// Never select the manage collection filter - rollback to the previous filter.
// This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value.
2020-09-11 07:02:46 +00:00
if (filter.NewValue is ManageCollectionsFilterMenuItem)
{
Current.Value = filter.OldValue;
manageCollectionsDialog?.Show();
return;
}
var newCollection = filter.NewValue.Collection;
// This dropdown be weird.
// We only care about filtering if the actual collection has changed.
if (newCollection != lastFiltered)
{
RequestFilter?.Invoke();
lastFiltered = newCollection;
}
2020-09-07 11:06:38 +00:00
}
2022-07-28 04:48:15 +00:00
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
realmSubscription?.Dispose();
}
2022-07-27 09:24:37 +00:00
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName;
2020-09-07 11:06:38 +00:00
protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader();
2020-09-07 11:06:38 +00:00
2020-09-11 07:08:49 +00:00
protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu();
protected virtual CollectionDropdownHeader CreateCollectionHeader() => new CollectionDropdownHeader();
protected virtual CollectionDropdownMenu CreateCollectionMenu() => new CollectionDropdownMenu();
2020-09-07 11:06:38 +00:00
2020-09-07 14:57:49 +00:00
public partial class CollectionDropdownHeader : OsuDropdownHeader
2020-09-07 11:06:38 +00:00
{
public CollectionDropdownHeader()
{
Height = 25;
Icon.Size = new Vector2(16);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
}
}
2020-09-11 07:01:01 +00:00
protected partial class CollectionDropdownMenu : OsuDropdownMenu
2020-09-07 11:06:38 +00:00
{
public CollectionDropdownMenu()
{
MaxHeight = 200;
}
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownDrawableMenuItem(item)
{
BackgroundColourHover = HoverColour,
BackgroundColourSelected = SelectionColour
};
2020-09-07 11:06:38 +00:00
}
protected partial class CollectionDropdownDrawableMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem
2020-09-07 11:06:38 +00:00
{
private IconButton addOrRemoveButton = null!;
2020-09-07 11:06:38 +00:00
2022-07-27 09:24:37 +00:00
private bool beatmapInCollection;
2022-07-28 04:48:15 +00:00
private readonly Live<BeatmapCollection>? collection;
2022-07-27 09:24:37 +00:00
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
public CollectionDropdownDrawableMenuItem(MenuItem item)
2020-09-07 11:06:38 +00:00
: base(item)
{
2022-07-28 04:48:15 +00:00
collection = ((DropdownMenuItem<CollectionFilterMenuItem>)item).Value.Collection;
2020-09-07 11:06:38 +00:00
}
[BackgroundDependencyLoader]
private void load()
{
2020-09-07 14:57:49 +00:00
AddInternal(addOrRemoveButton = new IconButton
2020-09-07 11:06:38 +00:00
{
2020-09-07 14:57:49 +00:00
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
X = -OsuScrollContainer.SCROLL_BAR_HEIGHT,
2020-09-08 05:36:38 +00:00
Scale = new Vector2(0.65f),
2020-09-08 04:45:26 +00:00
Action = addOrRemove,
2020-09-07 11:06:38 +00:00
});
}
protected override void LoadComplete()
{
base.LoadComplete();
2022-07-28 04:48:15 +00:00
if (collection != null)
2020-09-07 11:06:38 +00:00
{
2022-07-27 09:24:37 +00:00
beatmap.BindValueChanged(_ =>
{
2022-07-28 04:48:15 +00:00
beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash));
2022-07-27 09:24:37 +00:00
addOrRemoveButton.Enabled.Value = !beatmap.IsDefault;
addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare;
addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap";
updateButtonVisibility();
}, true);
}
2020-09-08 05:36:38 +00:00
updateButtonVisibility();
2020-09-07 11:06:38 +00:00
}
2020-09-08 04:45:26 +00:00
protected override bool OnHover(HoverEvent e)
{
updateButtonVisibility();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateButtonVisibility();
base.OnHoverLost(e);
}
protected override void OnSelectChange()
{
base.OnSelectChange();
updateButtonVisibility();
}
2020-09-08 05:36:38 +00:00
private void updateButtonVisibility()
{
2022-07-28 04:48:15 +00:00
if (collection == null)
2020-09-08 05:36:38 +00:00
addOrRemoveButton.Alpha = 0;
else
addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0;
}
2020-09-08 04:45:26 +00:00
2020-09-07 11:06:38 +00:00
private void addOrRemove()
{
2022-07-28 04:48:15 +00:00
Debug.Assert(collection != null);
2020-09-07 11:06:38 +00:00
2022-07-28 04:48:15 +00:00
collection.PerformWrite(c =>
{
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
});
2020-09-07 11:06:38 +00:00
}
2022-07-27 09:24:37 +00:00
protected override Drawable CreateContent() => (Content)base.CreateContent();
2020-09-07 11:06:38 +00:00
}
}
}