From 804bb33aedbc4f44a8aed6fc7c90d7528106b1df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 18:24:37 +0900 Subject: [PATCH] Hook up remaining data flows --- .../Collections/CollectionFilterDropdown.cs | 147 +++++------------- .../Collections/CollectionFilterMenuItem.cs | 21 +-- 2 files changed, 54 insertions(+), 114 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index c04f617eb9..ec409df4d6 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -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> collections = new BindableList>(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [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(), 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); } /// /// Occurs when a collection has been added or removed. /// - private void collectionsChanged() + private void collectionsChanged(IRealmCollection 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(); + } + } + } } - /// - /// Occurs when the selection has changed. - /// - private void filterChanged(ValueChangedEvent filter) + private void currentChanged(ValueChangedEvent 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 } } - /// - /// Occurs when the beatmaps contained by a have changed. - /// - 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 SelectedItem = new Bindable(); - private readonly Bindable collectionName = new Bindable(); - - 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)base.Item).Value; - [Resolved] - private IBindable beatmap { get; set; } = null!; - - private readonly Bindable collectionName; - private IconButton addOrRemoveButton = null!; - private Content content = null!; + private bool beatmapInCollection; - private IDisposable? realmSubscription; - - private Live? collection => Item.Collection; + [Resolved] + private IBindable 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(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(); } } } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index fd9e333915..2ac5784f09 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -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 /// /// The name of the collection. /// - public readonly Bindable CollectionName; + public string CollectionName { get; } /// /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem(Live? collection) + public CollectionFilterMenuItem(Live collection) + : this(collection.PerformRead(c => c.Name)) { Collection = collection; - CollectionName = new Bindable(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..."; } } }