1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:23:22 +08:00

Hook up remaining data flows

This commit is contained in:
Dean Herbert 2022-07-27 18:24:37 +09:00
parent 438067a18b
commit 804bb33aed
2 changed files with 54 additions and 114 deletions

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
@ -17,6 +16,7 @@ using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osuTK;
using Realms;
namespace osu.Game.Collections
{
@ -38,13 +38,15 @@ namespace osu.Game.Collections
set => current.Current = value;
}
private readonly IBindableList<Live<BeatmapCollection>> collections = new BindableList<Live<BeatmapCollection>>();
private readonly IBindableList<string> beatmaps = new BindableList<string>();
private readonly BindableList<CollectionFilterMenuItem> filters = new BindableList<CollectionFilterMenuItem>();
[Resolved]
private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
[Resolved]
private RealmAccess realm { get; set; } = null!;
public CollectionFilterDropdown()
{
ItemSource = filters;
@ -55,51 +57,49 @@ namespace osu.Game.Collections
{
base.LoadComplete();
// TODO: bind to realm data
realm.RegisterForNotifications(r => r.All<BeatmapCollection>(), collectionsChanged);
// Dropdown has logic which triggers a change on the bindable with every change to the contained items.
// This is not desirable here, as it leads to multiple filter operations running even though nothing has changed.
// An extra bindable is enough to subvert this behaviour.
base.Current = Current;
collections.BindCollectionChanged((_, _) => collectionsChanged(), true);
Current.BindValueChanged(filterChanged, true);
Current.BindValueChanged(currentChanged, true);
}
/// <summary>
/// Occurs when a collection has been added or removed.
/// </summary>
private void collectionsChanged()
private void collectionsChanged(IRealmCollection<BeatmapCollection> collections, ChangeSet? changes, Exception error)
{
var selectedItem = SelectedItem?.Value?.Collection;
filters.Clear();
filters.Add(new AllBeatmapsCollectionFilterMenuItem());
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c)));
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
if (ShowManageCollectionsItem)
filters.Add(new ManageCollectionsFilterMenuItem());
Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0];
// Trigger a re-filter if the current item was in the changeset.
if (selectedItem != null && changes != null)
{
foreach (int index in changes.ModifiedIndices)
{
if (collections[index].ID == selectedItem.ID)
{
// The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified.
// Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable.
Current.TriggerChange();
}
}
}
}
/// <summary>
/// Occurs when the <see cref="CollectionFilterMenuItem"/> selection has changed.
/// </summary>
private void filterChanged(ValueChangedEvent<CollectionFilterMenuItem> filter)
private void currentChanged(ValueChangedEvent<CollectionFilterMenuItem> filter)
{
// Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so.
beatmaps.CollectionChanged -= filterBeatmapsChanged;
// TODO: binding with realm
// if (filter.OldValue?.Collection != null)
// beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapMD5Hashes);
//
// if (filter.NewValue?.Collection != null)
// beatmaps.BindTo(filter.NewValue.Collection.BeatmapMD5Hashes);
beatmaps.CollectionChanged += filterBeatmapsChanged;
// 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.
if (filter.NewValue is ManageCollectionsFilterMenuItem)
@ -109,18 +109,7 @@ namespace osu.Game.Collections
}
}
/// <summary>
/// Occurs when the beatmaps contained by a <see cref="BeatmapCollection"/> have changed.
/// </summary>
private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// TODO: fuck this shit right off
// The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified.
// Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable.
Current.TriggerChange();
}
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value;
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName;
protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d =>
{
@ -136,13 +125,6 @@ namespace osu.Game.Collections
public class CollectionDropdownHeader : OsuDropdownHeader
{
public readonly Bindable<CollectionFilterMenuItem> SelectedItem = new Bindable<CollectionFilterMenuItem>();
private readonly Bindable<string> collectionName = new Bindable<string>();
protected override LocalisableString Label
{
get => base.Label;
set { } // See updateText().
}
public CollectionDropdownHeader()
{
@ -150,26 +132,6 @@ namespace osu.Game.Collections
Icon.Size = new Vector2(16);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
}
protected override void LoadComplete()
{
base.LoadComplete();
SelectedItem.BindValueChanged(_ => updateBindable(), true);
}
private void updateBindable()
{
collectionName.UnbindAll();
if (SelectedItem.Value != null)
collectionName.BindTo(SelectedItem.Value.CollectionName);
collectionName.BindValueChanged(_ => updateText(), true);
}
// Dropdowns don't bind to value changes, so the real name is copied directly from the selected item here.
private void updateText() => base.Label = collectionName.Value;
}
protected class CollectionDropdownMenu : OsuDropdownMenu
@ -190,23 +152,16 @@ namespace osu.Game.Collections
{
protected new CollectionFilterMenuItem Item => ((DropdownMenuItem<CollectionFilterMenuItem>)base.Item).Value;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
private readonly Bindable<string> collectionName;
private IconButton addOrRemoveButton = null!;
private Content content = null!;
private bool beatmapInCollection;
private IDisposable? realmSubscription;
private Live<BeatmapCollection>? collection => Item.Collection;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
public CollectionDropdownMenuItem(MenuItem item)
: base(item)
{
collectionName = Item.CollectionName.GetBoundCopy();
}
[BackgroundDependencyLoader]
@ -222,22 +177,25 @@ namespace osu.Game.Collections
});
}
[Resolved]
private RealmAccess realm { get; set; } = null!;
protected override void LoadComplete()
{
base.LoadComplete();
if (Item.Collection != null)
{
realmSubscription = realm.SubscribeToPropertyChanged(r => r.Find<BeatmapCollection>(Item.Collection.ID), c => c.BeatmapMD5Hashes, _ => hashesChanged());
beatmap.BindValueChanged(_ => hashesChanged(), true);
}
beatmap.BindValueChanged(_ =>
{
Debug.Assert(Item.Collection != null);
// Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge
// of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed.
collectionName.BindValueChanged(name => content.Text = name.NewValue, true);
beatmapInCollection = Item.Collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash));
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);
}
updateButtonVisibility();
}
@ -254,19 +212,6 @@ namespace osu.Game.Collections
base.OnHoverLost(e);
}
private void hashesChanged()
{
Debug.Assert(collection != null);
beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash));
addOrRemoveButton.Enabled.Value = !beatmap.IsDefault;
addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare;
addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap";
updateButtonVisibility();
}
protected override void OnSelectChange()
{
base.OnSelectChange();
@ -275,7 +220,7 @@ namespace osu.Game.Collections
private void updateButtonVisibility()
{
if (collection == null)
if (Item.Collection == null)
addOrRemoveButton.Alpha = 0;
else
addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0;
@ -283,22 +228,16 @@ namespace osu.Game.Collections
private void addOrRemove()
{
Debug.Assert(collection != null);
Debug.Assert(Item.Collection != null);
collection.PerformWrite(c =>
Item.Collection.PerformWrite(c =>
{
if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash))
c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash);
});
}
protected override Drawable CreateContent() => content = (Content)base.CreateContent();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
realmSubscription?.Dispose();
}
protected override Drawable CreateContent() => (Content)base.CreateContent();
}
}
}

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Bindables;
using osu.Game.Database;
namespace osu.Game.Collections
@ -21,19 +20,22 @@ namespace osu.Game.Collections
/// <summary>
/// The name of the collection.
/// </summary>
public readonly Bindable<string> CollectionName;
public string CollectionName { get; }
/// <summary>
/// Creates a new <see cref="CollectionFilterMenuItem"/>.
/// </summary>
/// <param name="collection">The collection to filter beatmaps from.</param>
public CollectionFilterMenuItem(Live<BeatmapCollection>? collection)
public CollectionFilterMenuItem(Live<BeatmapCollection> collection)
: this(collection.PerformRead(c => c.Name))
{
Collection = collection;
CollectionName = new Bindable<string>(collection?.PerformRead(c => c.Name) ?? "All beatmaps");
}
// TODO: track name changes i guess?
protected CollectionFilterMenuItem(string name)
{
CollectionName = name;
}
public bool Equals(CollectionFilterMenuItem? other)
{
@ -47,16 +49,16 @@ namespace osu.Game.Collections
// fallback to name-based comparison.
// this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below).
return CollectionName.Value == other.CollectionName.Value;
return CollectionName == other.CollectionName;
}
public override int GetHashCode() => CollectionName.Value.GetHashCode();
public override int GetHashCode() => CollectionName.GetHashCode();
}
public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem
{
public AllBeatmapsCollectionFilterMenuItem()
: base(null)
: base("All beatmaps")
{
}
}
@ -64,9 +66,8 @@ namespace osu.Game.Collections
public class ManageCollectionsFilterMenuItem : CollectionFilterMenuItem
{
public ManageCollectionsFilterMenuItem()
: base(null)
: base("Manage collections...")
{
CollectionName.Value = "Manage collections...";
}
}
}