mirror of
https://github.com/osukey/osukey.git
synced 2025-08-06 16:13:57 +09:00
Merge pull request #19399 from frenzibyte/carousel-order-stability
Maintain sort stability by using carousel item ID as a fallback
This commit is contained in:
@ -522,15 +522,15 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
var sets = new List<BeatmapSetInfo>();
|
var sets = new List<BeatmapSetInfo>();
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||||
|
|
||||||
// only need to set the first as they are a shared reference.
|
// only need to set the first as they are a shared reference.
|
||||||
var beatmap = set.Beatmaps.First();
|
var beatmap = set.Beatmaps.First();
|
||||||
|
|
||||||
beatmap.Metadata.Artist = "same artist";
|
beatmap.Metadata.Artist = $"artist {i / 2}";
|
||||||
beatmap.Metadata.Title = "same title";
|
beatmap.Metadata.Title = $"title {9 - i}";
|
||||||
|
|
||||||
sets.Add(set);
|
sets.Add(set);
|
||||||
}
|
}
|
||||||
@ -540,10 +540,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
loadBeatmaps(sets);
|
loadBeatmaps(sets);
|
||||||
|
|
||||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||||
AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == index + idOffset).All(b => b));
|
AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
|
||||||
|
|
||||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||||
AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == index + idOffset).All(b => b));
|
AddAssert("Items are in reverse order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + sets.Count - index - 1).All(b => b));
|
||||||
|
|
||||||
|
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||||
|
AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
namespace osu.Game.Screens.Select.Carousel
|
namespace osu.Game.Screens.Select.Carousel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A group which ensures only one child is selected.
|
/// A group which ensures only one item is selected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CarouselGroup : CarouselItem
|
public class CarouselGroup : CarouselItem
|
||||||
{
|
{
|
||||||
@ -18,13 +18,15 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
private List<CarouselItem> items = new List<CarouselItem>();
|
private List<CarouselItem> items = new List<CarouselItem>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to assign a monotonically increasing ID to children as they are added. This member is
|
/// Used to assign a monotonically increasing ID to items as they are added. This member is
|
||||||
/// incremented whenever a child is added.
|
/// incremented whenever an item is added.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ulong currentChildID;
|
private ulong currentItemID;
|
||||||
|
|
||||||
private Comparer<CarouselItem>? criteriaComparer;
|
private Comparer<CarouselItem>? criteriaComparer;
|
||||||
|
|
||||||
|
private static readonly Comparer<CarouselItem> item_id_comparer = Comparer<CarouselItem>.Create((x, y) => x.ItemID.CompareTo(y.ItemID));
|
||||||
|
|
||||||
private FilterCriteria? lastCriteria;
|
private FilterCriteria? lastCriteria;
|
||||||
|
|
||||||
protected int GetIndexOfItem(CarouselItem lastSelected) => items.IndexOf(lastSelected);
|
protected int GetIndexOfItem(CarouselItem lastSelected) => items.IndexOf(lastSelected);
|
||||||
@ -41,7 +43,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
public virtual void AddItem(CarouselItem i)
|
public virtual void AddItem(CarouselItem i)
|
||||||
{
|
{
|
||||||
i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue);
|
i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue);
|
||||||
i.ChildID = ++currentChildID;
|
i.ItemID = ++currentItemID;
|
||||||
|
|
||||||
if (lastCriteria != null)
|
if (lastCriteria != null)
|
||||||
{
|
{
|
||||||
@ -90,7 +92,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
// IEnumerable<T>.OrderBy() is used instead of List<T>.Sort() to ensure sorting stability
|
// IEnumerable<T>.OrderBy() is used instead of List<T>.Sort() to ensure sorting stability
|
||||||
criteriaComparer = Comparer<CarouselItem>.Create((x, y) => x.CompareTo(criteria, y));
|
criteriaComparer = Comparer<CarouselItem>.Create((x, y) => x.CompareTo(criteria, y));
|
||||||
items = items.OrderBy(c => c, criteriaComparer).ToList();
|
items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, item_id_comparer).ToList();
|
||||||
|
|
||||||
lastCriteria = criteria;
|
lastCriteria = criteria;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ using System.Linq;
|
|||||||
namespace osu.Game.Screens.Select.Carousel
|
namespace osu.Game.Screens.Select.Carousel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A group which ensures at least one child is selected (if the group itself is selected).
|
/// A group which ensures at least one item is selected (if the group itself is selected).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CarouselGroupEagerSelect : CarouselGroup
|
public class CarouselGroupEagerSelect : CarouselGroup
|
||||||
{
|
{
|
||||||
@ -35,16 +35,16 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To avoid overhead during filter operations, we don't attempt any selections until after all
|
/// To avoid overhead during filter operations, we don't attempt any selections until after all
|
||||||
/// children have been filtered. This bool will be true during the base <see cref="Filter(FilterCriteria)"/>
|
/// items have been filtered. This bool will be true during the base <see cref="Filter(FilterCriteria)"/>
|
||||||
/// operation.
|
/// operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool filteringChildren;
|
private bool filteringItems;
|
||||||
|
|
||||||
public override void Filter(FilterCriteria criteria)
|
public override void Filter(FilterCriteria criteria)
|
||||||
{
|
{
|
||||||
filteringChildren = true;
|
filteringItems = true;
|
||||||
base.Filter(criteria);
|
base.Filter(criteria);
|
||||||
filteringChildren = false;
|
filteringItems = false;
|
||||||
|
|
||||||
attemptSelection();
|
attemptSelection();
|
||||||
}
|
}
|
||||||
@ -97,12 +97,12 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
private void attemptSelection()
|
private void attemptSelection()
|
||||||
{
|
{
|
||||||
if (filteringChildren) return;
|
if (filteringItems) return;
|
||||||
|
|
||||||
// we only perform eager selection if we are a currently selected group.
|
// we only perform eager selection if we are a currently selected group.
|
||||||
if (State.Value != CarouselItemState.Selected) return;
|
if (State.Value != CarouselItemState.Selected) return;
|
||||||
|
|
||||||
// we only perform eager selection if none of our children are in a selected state already.
|
// we only perform eager selection if none of our items are in a selected state already.
|
||||||
if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return;
|
if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return;
|
||||||
|
|
||||||
PerformSelection();
|
PerformSelection();
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used as a default sort method for <see cref="CarouselItem"/>s of differing types.
|
/// Used as a default sort method for <see cref="CarouselItem"/>s of differing types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ulong ChildID;
|
internal ulong ItemID;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a fresh drawable version of this item.
|
/// Create a fresh drawable version of this item.
|
||||||
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ChildID.CompareTo(other.ChildID);
|
public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ItemID.CompareTo(other.ItemID);
|
||||||
|
|
||||||
public int CompareTo(CarouselItem other) => CarouselYPosition.CompareTo(other.CarouselYPosition);
|
public int CompareTo(CarouselItem other) => CarouselYPosition.CompareTo(other.CarouselYPosition);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user