1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-16 22:32:55 +08:00
osu-lazer/osu.Game/Collections/CollectionDropdown.cs

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

282 lines
10 KiB
C#
Raw Normal View History

2020-09-07 19:06:38 +08: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 19:06:38 +08:00
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
2020-09-07 19:06:38 +08:00
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
2020-09-08 12:45:26 +08:00
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
2020-09-07 19:06:38 +08:00
using osu.Game.Beatmaps;
using osu.Game.Database;
2020-09-07 19:06:38 +08:00
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osuTK;
2022-07-27 17:24:37 +08:00
using Realms;
2020-09-07 19:06:38 +08:00
2020-09-11 15:01:01 +08:00
namespace osu.Game.Collections
2020-09-07 19:06:38 +08:00
{
2020-09-07 20:08:48 +08:00
/// <summary>
2022-07-28 12:48:15 +08:00
/// A dropdown to select the collection to be used to filter results.
2020-09-07 20:08:48 +08:00
/// </summary>
2022-07-28 12:48:15 +08:00
public partial class CollectionDropdown : OsuDropdown<CollectionFilterMenuItem>
2020-09-07 19:06:38 +08: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 15:02:46 +08:00
private readonly BindableList<CollectionFilterMenuItem> filters = new BindableList<CollectionFilterMenuItem>();
2020-09-07 19:06:38 +08:00
[Resolved]
private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
2022-07-27 17:24:37 +08:00
[Resolved]
private RealmAccess realm { get; set; } = null!;
2022-07-28 12:48:15 +08:00
private IDisposable? realmSubscription;
private readonly CollectionFilterMenuItem allBeatmapsItem = new AllBeatmapsCollectionFilterMenuItem();
2022-07-28 12:48:15 +08:00
public CollectionDropdown()
2020-09-07 19:06:38 +08:00
{
ItemSource = filters;
Current.Value = allBeatmapsItem;
AlwaysShowSearchBar = true;
2020-09-07 19:06:38 +08:00
}
protected override void LoadComplete()
2020-09-07 19:06:38 +08:00
{
base.LoadComplete();
2022-07-28 13:07:42 +08:00
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapCollection>().OrderBy(c => c.Name), collectionsChanged);
2020-09-09 15:33:48 +08:00
Current.BindValueChanged(selectionChanged);
2020-09-07 19:06:38 +08:00
}
2023-07-06 12:37:42 +08:00
private void collectionsChanged(IRealmCollection<BeatmapCollection> collections, ChangeSet? changes)
2020-09-07 19:06:38 +08:00
{
if (changes == null)
{
filters.Clear();
filters.Add(allBeatmapsItem);
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
if (ShowManageCollectionsItem)
filters.Add(new ManageCollectionsFilterMenuItem());
}
else
{
foreach (int i in changes.DeletedIndices.OrderDescending())
filters.RemoveAt(i + 1);
foreach (int i in changes.InsertedIndices)
filters.Insert(i + 1, new CollectionFilterMenuItem(collections[i].ToLive(realm)));
2022-07-27 17:24:37 +08:00
var selectedItem = SelectedItem?.Value;
foreach (int i in changes.NewModifiedIndices)
2022-07-27 17:24:37 +08:00
{
var updatedItem = collections[i];
// This is responsible for updating the state of the +/- button and the collection's name.
// TODO: we can probably make the menu items update with changes to avoid this.
filters.RemoveAt(i + 1);
filters.Insert(i + 1, new CollectionFilterMenuItem(updatedItem.ToLive(realm)));
if (updatedItem.ID == selectedItem?.Collection?.ID)
{
// 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 = allBeatmapsItem;
Schedule(() =>
{
// current may have changed before the scheduled call is run.
if (Current.Value != allBeatmapsItem)
return;
Current.Value = filters.SingleOrDefault(f => f.Collection?.ID == selectedItem.Collection?.ID) ?? filters[0];
});
// Trigger an external re-filter if the current item was in the change set.
RequestFilter?.Invoke();
break;
}
2022-07-27 17:24:37 +08:00
}
}
2020-09-07 19:06:38 +08:00
}
private Live<BeatmapCollection>? lastFiltered;
private void selectionChanged(ValueChangedEvent<CollectionFilterMenuItem> filter)
2020-09-07 19:06:38 +08: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 15:02:46 +08: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 19:06:38 +08:00
}
2022-07-28 12:48:15 +08:00
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
realmSubscription?.Dispose();
}
2022-07-27 17:24:37 +08:00
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName;
2020-09-07 19:06:38 +08:00
protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader();
2020-09-07 19:06:38 +08:00
2020-09-11 15:08:49 +08:00
protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu();
protected virtual CollectionDropdownHeader CreateCollectionHeader() => new CollectionDropdownHeader();
protected virtual CollectionDropdownMenu CreateCollectionMenu() => new CollectionDropdownMenu();
2020-09-07 19:06:38 +08:00
2020-09-07 22:57:49 +08:00
public partial class CollectionDropdownHeader : OsuDropdownHeader
2020-09-07 19:06:38 +08:00
{
public CollectionDropdownHeader()
{
Height = 25;
Chevron.Size = new Vector2(12);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 8 };
2020-09-07 19:06:38 +08:00
}
}
2020-09-11 15:01:01 +08:00
protected partial class CollectionDropdownMenu : OsuDropdownMenu
2020-09-07 19:06:38 +08:00
{
public CollectionDropdownMenu()
{
MaxHeight = 200;
}
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownDrawableMenuItem(item)
{
BackgroundColourHover = HoverColour,
BackgroundColourSelected = SelectionColour
};
2020-09-07 19:06:38 +08:00
}
protected partial class CollectionDropdownDrawableMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem
2020-09-07 19:06:38 +08:00
{
private IconButton addOrRemoveButton = null!;
2020-09-07 19:06:38 +08:00
2022-07-27 17:24:37 +08:00
private bool beatmapInCollection;
2022-07-28 12:48:15 +08:00
private readonly Live<BeatmapCollection>? collection;
2022-07-27 17:24:37 +08:00
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
public CollectionDropdownDrawableMenuItem(MenuItem item)
2020-09-07 19:06:38 +08:00
: base(item)
{
2022-07-28 12:48:15 +08:00
collection = ((DropdownMenuItem<CollectionFilterMenuItem>)item).Value.Collection;
2020-09-07 19:06:38 +08:00
}
[BackgroundDependencyLoader]
private void load()
{
2024-05-23 01:20:58 +08:00
AddInternal(addOrRemoveButton = new NoFocusChangeIconButton
2020-09-07 19:06:38 +08:00
{
2020-09-07 22:57:49 +08:00
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
X = -OsuScrollContainer.SCROLL_BAR_WIDTH,
2020-09-08 13:36:38 +08:00
Scale = new Vector2(0.65f),
2020-09-08 12:45:26 +08:00
Action = addOrRemove,
2020-09-07 19:06:38 +08:00
});
}
protected override void LoadComplete()
{
base.LoadComplete();
2022-07-28 12:48:15 +08:00
if (collection != null)
2020-09-07 19:06:38 +08:00
{
2022-07-27 17:24:37 +08:00
beatmap.BindValueChanged(_ =>
{
2022-07-28 12:48:15 +08:00
beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash));
2022-07-27 17:24:37 +08: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 13:36:38 +08:00
updateButtonVisibility();
2020-09-07 19:06:38 +08:00
}
2020-09-08 12:45:26 +08: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 13:36:38 +08:00
private void updateButtonVisibility()
{
2022-07-28 12:48:15 +08:00
if (collection == null)
2020-09-08 13:36:38 +08:00
addOrRemoveButton.Alpha = 0;
else
addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0;
}
2020-09-08 12:45:26 +08:00
2020-09-07 19:06:38 +08:00
private void addOrRemove()
{
2022-07-28 12:48:15 +08:00
Debug.Assert(collection != null);
2020-09-07 19:06:38 +08:00
2022-07-28 12:48:15 +08:00
collection.PerformWrite(c =>
{
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
});
2020-09-07 19:06:38 +08:00
}
2022-07-27 17:24:37 +08:00
protected override Drawable CreateContent() => (Content)base.CreateContent();
2024-05-23 01:20:58 +08:00
private partial class NoFocusChangeIconButton : IconButton
{
public override bool ChangeFocusOnClick => false;
}
2020-09-07 19:06:38 +08:00
}
}
}