Skip to content

Commit

Permalink
Merge pull request #25575 from frenzibyte/collection-dropdown-improve-ux
Browse files Browse the repository at this point in the history
Improve handling of beatmap collection changes in `CollectionDropdown`
  • Loading branch information
peppy authored Dec 14, 2023
2 parents 59355dc + 31c9489 commit 76653e6
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 32 deletions.
17 changes: 11 additions & 6 deletions osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ public void TestManageCollectionsFilterIsNotSelected()

AddStep("select collection", () =>
{
InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1));
InputManager.MoveMouseTo(getCollectionDropdownItemAt(1));
InputManager.Click(MouseButton.Left);
});

Expand All @@ -206,7 +206,8 @@ public void TestManageCollectionsFilterIsNotSelected()

AddStep("click manage collections filter", () =>
{
InputManager.MoveMouseTo(getCollectionDropdownItems().Last());
int lastItemIndex = control.ChildrenOfType<CollectionDropdown>().Single().Items.Count() - 1;
InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex));
InputManager.Click(MouseButton.Left);
});

Expand All @@ -232,10 +233,10 @@ private void assertCollectionHeaderDisplays(string collectionName, bool shouldDi
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName)));
() => shouldContain == control.ChildrenOfType<Menu.DrawableMenuItem>().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName));

private IconButton getAddOrRemoveButton(int index)
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
=> getCollectionDropdownItemAt(index).ChildrenOfType<IconButton>().Single();

private void addExpandHeaderStep() => AddStep("expand header", () =>
{
Expand All @@ -249,7 +250,11 @@ private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or r
InputManager.Click(MouseButton.Left);
});

private IEnumerable<Dropdown<CollectionFilterMenuItem>.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems()
=> control.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Dropdown<CollectionFilterMenuItem>.DropdownMenu.DrawableDropdownMenuItem>();
private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index)
{
// todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079
CollectionFilterMenuItem item = control.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
return control.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
}
}
}
68 changes: 42 additions & 26 deletions osu.Game/Collections/CollectionDropdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ public partial class CollectionDropdown : OsuDropdown<CollectionFilterMenuItem>

private IDisposable? realmSubscription;

private readonly CollectionFilterMenuItem allBeatmapsItem = new AllBeatmapsCollectionFilterMenuItem();

public CollectionDropdown()
{
ItemSource = filters;

Current.Value = new AllBeatmapsCollectionFilterMenuItem();
Current.Value = allBeatmapsItem;
}

protected override void LoadComplete()
Expand All @@ -61,37 +63,51 @@ protected override void LoadComplete()

private void collectionsChanged(IRealmCollection<BeatmapCollection> collections, ChangeSet? changes)
{
var selectedItem = SelectedItem?.Value?.Collection;

var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem();
if (changes == null)
{
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)
filters.RemoveAt(i + 1);

filters.Clear();
filters.Add(allBeatmaps);
filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
foreach (int i in changes.InsertedIndices)
filters.Insert(i + 1, new CollectionFilterMenuItem(collections[i].ToLive(realm)));

if (ShowManageCollectionsItem)
filters.Add(new ManageCollectionsFilterMenuItem());
var selectedItem = SelectedItem?.Value;

// 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;
foreach (int i in changes.NewModifiedIndices)
{
var updatedItem = collections[i];

Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0];
});
// 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)));

// Trigger a re-filter if the current item was in the change set.
if (selectedItem != null && changes != null)
{
foreach (int index in changes.ModifiedIndices)
{
if (collections[index].ID == selectedItem.ID)
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;
}
}
}
}
Expand Down

0 comments on commit 76653e6

Please sign in to comment.