mirror of
https://github.com/ppy/osu.git
synced 2026-05-23 22:44:54 +08:00
Merge pull request #33046 from frenzibyte/carousel-filtering
Add filtering support in carousel v2
This commit is contained in:
@@ -20,6 +20,7 @@ using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
@@ -127,12 +128,35 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Prefer title sorting so that order of carousel panels match order of BeatmapSets bindable.
|
||||
SortBy(SortMode.Title);
|
||||
}
|
||||
|
||||
protected void SortBy(FilterCriteria criteria) => AddStep($"sort:{criteria.Sort} group:{criteria.Group}", () => Carousel.Filter(criteria));
|
||||
protected void SortBy(SortMode mode) => ApplyToFilter($"sort by {mode.ToString().ToLowerInvariant()}", c => c.Sort = mode);
|
||||
protected void GroupBy(GroupMode mode) => ApplyToFilter($"group by {mode.ToString().ToLowerInvariant()}", c => c.Group = mode);
|
||||
|
||||
protected void SortAndGroupBy(SortMode sort, GroupMode group)
|
||||
{
|
||||
ApplyToFilter($"sort by {sort.ToString().ToLowerInvariant()} & group by {group.ToString().ToLowerInvariant()}", c =>
|
||||
{
|
||||
c.Sort = sort;
|
||||
c.Group = group;
|
||||
});
|
||||
}
|
||||
|
||||
protected void ApplyToFilter(string description, Action<FilterCriteria>? apply)
|
||||
{
|
||||
AddStep(description, () =>
|
||||
{
|
||||
var criteria = Carousel.Criteria;
|
||||
apply?.Invoke(criteria);
|
||||
Carousel.Filter(criteria);
|
||||
});
|
||||
}
|
||||
|
||||
protected void WaitForDrawablePanels() => AddUntilStep("drawable panels loaded", () => Carousel.ChildrenOfType<ICarouselPanel>().Count(), () => Is.GreaterThan(0));
|
||||
protected void WaitForSorting() => AddUntilStep("sorting finished", () => Carousel.IsFiltering, () => Is.False);
|
||||
protected void WaitForFiltering() => AddUntilStep("filtering finished", () => Carousel.IsFiltering, () => Is.False);
|
||||
protected void WaitForScrolling() => AddUntilStep("scroll finished", () => Scroll.Current, () => Is.EqualTo(Scroll.Target));
|
||||
|
||||
protected void SelectNextPanel() => AddStep("select next panel", () => InputManager.Key(Key.Down));
|
||||
@@ -145,6 +169,32 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
protected void CheckNoSelection() => AddAssert("has no selection", () => Carousel.CurrentSelection, () => Is.Null);
|
||||
protected void CheckHasSelection() => AddAssert("has selection", () => Carousel.CurrentSelection, () => Is.Not.Null);
|
||||
|
||||
protected void CheckDisplayedBeatmapsCount(int expected)
|
||||
{
|
||||
AddAssert($"{expected} diffs displayed", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
protected void CheckDisplayedBeatmapSetsCount(int expected)
|
||||
{
|
||||
AddAssert($"{expected} sets displayed", () =>
|
||||
{
|
||||
var groupingFilter = Carousel.Filters.OfType<BeatmapCarouselFilterGrouping>().Single();
|
||||
|
||||
// Using groupingFilter.SetItems.Count alone doesn't work.
|
||||
// When sorting by difficulty, there can be more than one set panel for the same set displayed.
|
||||
return groupingFilter.SetItems.Sum(s => s.Value.Count(i => i.Model is BeatmapSetInfo));
|
||||
}, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
protected void CheckDisplayedGroupsCount(int expected)
|
||||
{
|
||||
AddAssert($"{expected} groups displayed", () =>
|
||||
{
|
||||
var groupingFilter = Carousel.Filters.OfType<BeatmapCarouselFilterGrouping>().Single();
|
||||
return groupingFilter.GroupItems.Count;
|
||||
}, () => Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
protected ICarouselPanel? GetSelectedPanel() => Carousel.ChildrenOfType<ICarouselPanel>().SingleOrDefault(p => p.Selected.Value);
|
||||
protected ICarouselPanel? GetKeyboardSelectedPanel() => Carousel.ChildrenOfType<ICarouselPanel>().SingleOrDefault(p => p.KeyboardSelected.Value);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
@@ -34,9 +33,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Explicit]
|
||||
public void TestSorting()
|
||||
{
|
||||
SortBy(new FilterCriteria { Sort = SortMode.Artist });
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.All);
|
||||
SortAndGroupBy(SortMode.Difficulty, GroupMode.Difficulty);
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.Artist);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
@@ -19,7 +18,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Artist, Sort = SortMode.Artist });
|
||||
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.Artist);
|
||||
|
||||
AddBeatmaps(10, 3, true);
|
||||
WaitForDrawablePanels();
|
||||
@@ -173,5 +173,37 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SelectNextGroup();
|
||||
WaitForGroupSelection(1, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicFiltering()
|
||||
{
|
||||
ApplyToFilter("filter", c => c.SearchText = BeatmapSets[2].Metadata.Title);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedGroupsCount(1);
|
||||
CheckDisplayedBeatmapSetsCount(1);
|
||||
CheckDisplayedBeatmapsCount(3);
|
||||
|
||||
CheckNoSelection();
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
WaitForGroupSelection(0, 1);
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
SelectNextPanel();
|
||||
|
||||
Select();
|
||||
|
||||
WaitForGroupSelection(0, 2);
|
||||
|
||||
ApplyToFilter("remove filter", c => c.SearchText = string.Empty);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedGroupsCount(5);
|
||||
CheckDisplayedBeatmapSetsCount(10);
|
||||
CheckDisplayedBeatmapsCount(30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
@@ -21,7 +20,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria { Group = GroupMode.Difficulty, Sort = SortMode.Difficulty });
|
||||
|
||||
SortAndGroupBy(SortMode.Difficulty, GroupMode.Difficulty);
|
||||
|
||||
AddBeatmaps(10, 3);
|
||||
WaitForDrawablePanels();
|
||||
@@ -191,5 +191,37 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
ClickVisiblePanelWithOffset<PanelBeatmap>(1, new Vector2(0, (CarouselItem.DEFAULT_HEIGHT / 2 + 1)));
|
||||
WaitForGroupSelection(0, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicFiltering()
|
||||
{
|
||||
ApplyToFilter("filter", c => c.SearchText = BeatmapSets[2].Metadata.Title);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedGroupsCount(3);
|
||||
CheckDisplayedBeatmapsCount(3);
|
||||
|
||||
CheckNoSelection();
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
WaitForGroupSelection(0, 0);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
SelectNextPanel();
|
||||
|
||||
Select();
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
||||
WaitForGroupSelection(1, 0);
|
||||
|
||||
ApplyToFilter("remove filter", c => c.SearchText = string.Empty);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedGroupsCount(3);
|
||||
CheckDisplayedBeatmapsCount(30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// 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;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselFiltering : BeatmapCarouselTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicFiltering()
|
||||
{
|
||||
AddBeatmaps(10, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
ApplyToFilter("filter", c => c.SearchText = BeatmapSets[2].Metadata.Title);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedBeatmapSetsCount(1);
|
||||
CheckDisplayedBeatmapsCount(3);
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
||||
WaitForSelection(2, 0);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
SelectNextPanel();
|
||||
|
||||
Select();
|
||||
WaitForSelection(2, 1);
|
||||
|
||||
ApplyToFilter("remove filter", c => c.SearchText = string.Empty);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedBeatmapSetsCount(10);
|
||||
CheckDisplayedBeatmapsCount(30);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFilteringByUserStarDifficulty()
|
||||
{
|
||||
AddStep("add mixed difficulty set", () =>
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
set.Beatmaps.Clear();
|
||||
|
||||
for (int i = 1; i <= 15; i++)
|
||||
{
|
||||
set.Beatmaps.Add(new BeatmapInfo(new OsuRuleset().RulesetInfo, new BeatmapDifficulty(), new BeatmapMetadata())
|
||||
{
|
||||
BeatmapSet = set,
|
||||
DifficultyName = $"Stars: {i}",
|
||||
StarRating = i,
|
||||
});
|
||||
}
|
||||
|
||||
BeatmapSets.Add(set);
|
||||
});
|
||||
|
||||
WaitForDrawablePanels();
|
||||
|
||||
ApplyToFilter("filter [5..]", c =>
|
||||
{
|
||||
c.UserStarDifficulty.Min = 5;
|
||||
c.UserStarDifficulty.Max = null;
|
||||
});
|
||||
WaitForFiltering();
|
||||
CheckDisplayedBeatmapsCount(11);
|
||||
|
||||
ApplyToFilter("filter to [0..7]", c =>
|
||||
{
|
||||
c.UserStarDifficulty.Min = null;
|
||||
c.UserStarDifficulty.Max = 7;
|
||||
});
|
||||
WaitForFiltering();
|
||||
CheckDisplayedBeatmapsCount(7);
|
||||
|
||||
ApplyToFilter("filter to [5..7]", c =>
|
||||
{
|
||||
c.UserStarDifficulty.Min = 5;
|
||||
c.UserStarDifficulty.Max = 7;
|
||||
});
|
||||
|
||||
WaitForFiltering();
|
||||
CheckDisplayedBeatmapsCount(3);
|
||||
|
||||
ApplyToFilter("filter to [2..2]", c =>
|
||||
{
|
||||
c.UserStarDifficulty.Min = 2;
|
||||
c.UserStarDifficulty.Max = 2;
|
||||
});
|
||||
|
||||
WaitForFiltering();
|
||||
CheckDisplayedBeatmapsCount(1);
|
||||
|
||||
ApplyToFilter("filter to [0..]", c =>
|
||||
{
|
||||
c.UserStarDifficulty.Min = 0;
|
||||
c.UserStarDifficulty.Max = null;
|
||||
});
|
||||
WaitForFiltering();
|
||||
CheckDisplayedBeatmapsCount(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCarouselRemembersSelection()
|
||||
{
|
||||
Guid selectedID = Guid.Empty;
|
||||
|
||||
AddBeatmaps(50, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextGroup();
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
||||
AddStep("record selection", () => selectedID = ((BeatmapInfo)Carousel.CurrentSelection!).ID);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ApplyToFilter("filter all", c => c.SearchText = Guid.NewGuid().ToString());
|
||||
AddAssert("selection not changed", () => ((BeatmapInfo)Carousel.CurrentSelection!).ID == selectedID);
|
||||
ApplyToFilter("remove filter", c => c.SearchText = string.Empty);
|
||||
AddAssert("selection not changed", () => ((BeatmapInfo)Carousel.CurrentSelection!).ID == selectedID);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCarouselRemembersSelectionDifficultySort()
|
||||
{
|
||||
Guid selectedID = Guid.Empty;
|
||||
|
||||
AddBeatmaps(50, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SortBy(SortMode.Difficulty);
|
||||
|
||||
SelectNextGroup();
|
||||
|
||||
AddStep("record selection", () => selectedID = ((BeatmapInfo)Carousel.CurrentSelection!).ID);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
ApplyToFilter("filter all", c => c.SearchText = Guid.NewGuid().ToString());
|
||||
AddAssert("selection not changed", () => ((BeatmapInfo)Carousel.CurrentSelection!).ID == selectedID);
|
||||
ApplyToFilter("remove filter", c => c.SearchText = string.Empty);
|
||||
AddAssert("selection not changed", () => ((BeatmapInfo)Carousel.CurrentSelection!).ID == selectedID);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCarouselRetainsSelectionFromDifficultySort()
|
||||
{
|
||||
AddBeatmaps(50, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
BeatmapInfo chosenBeatmap = null!;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
int diff = i;
|
||||
|
||||
AddStep($"select diff {diff}", () => Carousel.CurrentSelection = chosenBeatmap = BeatmapSets[20].Beatmaps[diff]);
|
||||
AddUntilStep("selection changed", () => Carousel.CurrentSelection, () => Is.EqualTo(chosenBeatmap));
|
||||
|
||||
SortBy(SortMode.Difficulty);
|
||||
AddAssert("selection retained", () => Carousel.CurrentSelection, () => Is.EqualTo(chosenBeatmap));
|
||||
|
||||
SortBy(SortMode.Title);
|
||||
AddAssert("selection retained", () => Carousel.CurrentSelection, () => Is.EqualTo(chosenBeatmap));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExternalRulesetChange()
|
||||
{
|
||||
ApplyToFilter("allow converted beatmaps", c => c.AllowConvertedBeatmaps = true);
|
||||
ApplyToFilter("filter to osu", c => c.Ruleset = rulesets.AvailableRulesets.ElementAt(0));
|
||||
|
||||
WaitForFiltering();
|
||||
|
||||
AddStep("add mixed ruleset beatmapset", () =>
|
||||
{
|
||||
var testMixed = TestResources.CreateTestBeatmapSetInfo(3);
|
||||
|
||||
for (int i = 0; i <= 2; i++)
|
||||
testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i);
|
||||
|
||||
BeatmapSets.Add(testMixed);
|
||||
});
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SelectNextPanel();
|
||||
Select();
|
||||
|
||||
AddUntilStep("wait for filtered difficulties", () =>
|
||||
{
|
||||
var visibleBeatmapPanels = GetVisiblePanels<PanelBeatmap>();
|
||||
|
||||
return visibleBeatmapPanels.Count() == 1
|
||||
&& visibleBeatmapPanels.Count(p => ((BeatmapInfo)p.Item!.Model).Ruleset.OnlineID == 0) == 1;
|
||||
});
|
||||
|
||||
ApplyToFilter("filter to taiko", c => c.Ruleset = rulesets.AvailableRulesets.ElementAt(1));
|
||||
|
||||
WaitForFiltering();
|
||||
|
||||
AddUntilStep("wait for filtered difficulties", () =>
|
||||
{
|
||||
var visibleBeatmapPanels = GetVisiblePanels<PanelBeatmap>();
|
||||
|
||||
return visibleBeatmapPanels.Count() == 2
|
||||
&& visibleBeatmapPanels.Count(p => ((BeatmapInfo)p.Item!.Model).Ruleset.OnlineID == 0) == 1
|
||||
&& visibleBeatmapPanels.Count(p => ((BeatmapInfo)p.Item!.Model).Ruleset.OnlineID == 1) == 1;
|
||||
});
|
||||
|
||||
ApplyToFilter("filter to catch", c => c.Ruleset = rulesets.AvailableRulesets.ElementAt(2));
|
||||
|
||||
WaitForFiltering();
|
||||
|
||||
AddUntilStep("wait for filtered difficulties", () =>
|
||||
{
|
||||
var visibleBeatmapPanels = GetVisiblePanels<PanelBeatmap>();
|
||||
|
||||
return visibleBeatmapPanels.Count() == 2
|
||||
&& visibleBeatmapPanels.Count(p => ((BeatmapInfo)p.Item!.Model).Ruleset.OnlineID == 0) == 1
|
||||
&& visibleBeatmapPanels.Count(p => ((BeatmapInfo)p.Item!.Model).Ruleset.OnlineID == 2) == 1;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("Difficulty sorting is broken when set headers are included.")] // todo: fix.
|
||||
public void TestSortingWithDifficultyFiltered()
|
||||
{
|
||||
const int diffs_per_set = 3;
|
||||
const int local_set_count = 2;
|
||||
|
||||
AddStep("populate beatmap sets", () =>
|
||||
{
|
||||
for (int i = 0; i < local_set_count; i++)
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo(diffs_per_set);
|
||||
set.Beatmaps[0].StarRating = 3 - i;
|
||||
set.Beatmaps[0].DifficultyName += $" ({3 - i}*)";
|
||||
set.Beatmaps[1].StarRating = 6 + i;
|
||||
set.Beatmaps[1].DifficultyName += $" ({6 + i}*)";
|
||||
BeatmapSets.Add(set);
|
||||
}
|
||||
});
|
||||
|
||||
SortBy(SortMode.Difficulty);
|
||||
WaitForFiltering();
|
||||
|
||||
CheckDisplayedBeatmapSetsCount(3);
|
||||
CheckDisplayedBeatmapsCount(local_set_count * diffs_per_set);
|
||||
|
||||
ApplyToFilter("filter to normal", c => c.SearchText = "Normal");
|
||||
|
||||
CheckDisplayedBeatmapSetsCount(local_set_count);
|
||||
CheckDisplayedBeatmapsCount(local_set_count);
|
||||
|
||||
ApplyToFilter("filter to insane", c => c.SearchText = "Insane");
|
||||
|
||||
CheckDisplayedBeatmapSetsCount(local_set_count);
|
||||
CheckDisplayedBeatmapsCount(local_set_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
@@ -22,7 +20,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria { Sort = SortMode.Title });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
@@ -18,7 +18,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RemoveAllBeatmaps();
|
||||
CreateCarousel();
|
||||
SortBy(new FilterCriteria());
|
||||
|
||||
SortBy(SortMode.Artist);
|
||||
|
||||
AddBeatmaps(10);
|
||||
WaitForDrawablePanels();
|
||||
@@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<PanelBeatmap>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<PanelBeatmap>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
@@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddStep("save selected screen position", () => positionBefore = Carousel.ChildrenOfType<PanelBeatmap>().FirstOrDefault(p => p.Selected.Value)!.ScreenSpaceDrawQuad);
|
||||
|
||||
RemoveFirstBeatmap();
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
AddAssert("select screen position unchanged", () => Carousel.ChildrenOfType<PanelBeatmap>().Single(p => p.Selected.Value).ScreenSpaceDrawQuad,
|
||||
() => Is.EqualTo(positionBefore));
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
BeatmapSets.Add(baseTestBeatmap);
|
||||
});
|
||||
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
AddStep("update beatmap with same reference", () => BeatmapSets.ReplaceRange(1, 1, [baseTestBeatmap]));
|
||||
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
AddAssert("drawables unchanged", () => Carousel.ChildrenOfType<Panel>(), () => Is.EqualTo(originalDrawables));
|
||||
}
|
||||
|
||||
@@ -78,21 +78,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
updateBeatmap(b => b.Metadata = metadata);
|
||||
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
AddAssert("drawables changed", () => Carousel.ChildrenOfType<Panel>(), () => Is.Not.EqualTo(originalDrawables));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectionHeld()
|
||||
{
|
||||
SelectPrevGroup();
|
||||
SelectNextGroup();
|
||||
|
||||
WaitForSelection(1, 0);
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
updateBeatmap();
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
@@ -101,14 +101,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test] // Checks that we keep selection based on online ID where possible.
|
||||
public void TestSelectionHeldDifficultyNameChanged()
|
||||
{
|
||||
SelectPrevGroup();
|
||||
SelectNextGroup();
|
||||
|
||||
WaitForSelection(1, 0);
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
updateBeatmap(b => b.DifficultyName = "new name");
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
@@ -117,14 +117,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test] // Checks that we fallback to keeping selection based on difficulty name.
|
||||
public void TestSelectionHeldDifficultyOnlineIDChanged()
|
||||
{
|
||||
SelectPrevGroup();
|
||||
SelectNextGroup();
|
||||
|
||||
WaitForSelection(1, 0);
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
updateBeatmap(b => b.OnlineID = b.OnlineID + 1);
|
||||
WaitForSorting();
|
||||
WaitForFiltering();
|
||||
|
||||
AddAssert("selection is updateable beatmap", () => Carousel.CurrentSelection, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
AddAssert("visible panel is updateable beatmap", () => GetSelectedPanel()?.Item?.Model, () => Is.EqualTo(baseTestBeatmap.Beatmaps[0]));
|
||||
|
||||
@@ -31,8 +31,14 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
private readonly LoadingLayer loading;
|
||||
|
||||
private readonly BeatmapCarouselFilterMatching matching;
|
||||
private readonly BeatmapCarouselFilterGrouping grouping;
|
||||
|
||||
/// <summary>
|
||||
/// Total number of beatmap difficulties displayed with the filter.
|
||||
/// </summary>
|
||||
public int MatchedBeatmapsCount => matching.BeatmapItemsCount;
|
||||
|
||||
protected override float GetSpacingBetweenPanels(CarouselItem top, CarouselItem bottom)
|
||||
{
|
||||
if (top.Model is BeatmapInfo || bottom.Model is BeatmapInfo)
|
||||
@@ -49,6 +55,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
Filters = new ICarouselFilter[]
|
||||
{
|
||||
matching = new BeatmapCarouselFilterMatching(() => Criteria),
|
||||
new BeatmapCarouselFilterSorting(() => Criteria),
|
||||
grouping = new BeatmapCarouselFilterGrouping(() => Criteria),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public class BeatmapCarouselFilterMatching : ICarouselFilter
|
||||
{
|
||||
private readonly Func<FilterCriteria> getCriteria;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of beatmap difficulties displayed post filter.
|
||||
/// </summary>
|
||||
public int BeatmapItemsCount { get; private set; }
|
||||
|
||||
public BeatmapCarouselFilterMatching(Func<FilterCriteria> getCriteria)
|
||||
{
|
||||
this.getCriteria = getCriteria;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CarouselItem>> Run(IEnumerable<CarouselItem> items, CancellationToken cancellationToken) => await Task.Run(() =>
|
||||
{
|
||||
var criteria = getCriteria();
|
||||
|
||||
return matchItems(items, criteria);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
private IEnumerable<CarouselItem> matchItems(IEnumerable<CarouselItem> items, FilterCriteria criteria)
|
||||
{
|
||||
int countMatching = 0;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var beatmap = (BeatmapInfo)item.Model;
|
||||
|
||||
if (checkMatch(beatmap, criteria))
|
||||
{
|
||||
countMatching++;
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
BeatmapItemsCount = countMatching;
|
||||
}
|
||||
|
||||
private static bool checkMatch(BeatmapInfo beatmap, FilterCriteria criteria)
|
||||
{
|
||||
bool match = criteria.Ruleset == null ||
|
||||
beatmap.Ruleset.ShortName == criteria.Ruleset.ShortName ||
|
||||
(beatmap.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps);
|
||||
|
||||
if (beatmap.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
|
||||
{
|
||||
// only check ruleset equality or convertability for selected beatmap
|
||||
return match;
|
||||
}
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
if (criteria.SearchTerms.Length > 0)
|
||||
{
|
||||
match = beatmap.Match(criteria.SearchTerms);
|
||||
|
||||
// if a match wasn't found via text matching of terms, do a second catch-all check matching against online IDs.
|
||||
// this should be done after text matching so we can prioritise matching numbers in metadata.
|
||||
if (!match && criteria.SearchNumber.HasValue)
|
||||
{
|
||||
match = (beatmap.OnlineID == criteria.SearchNumber.Value) ||
|
||||
(beatmap.BeatmapSet?.OnlineID == criteria.SearchNumber.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(beatmap.StarRating);
|
||||
match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(beatmap.Difficulty.ApproachRate);
|
||||
match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(beatmap.Difficulty.DrainRate);
|
||||
match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(beatmap.Difficulty.CircleSize);
|
||||
match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(beatmap.Difficulty.OverallDifficulty);
|
||||
match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(beatmap.Length);
|
||||
match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(beatmap.LastPlayed ?? DateTimeOffset.MinValue);
|
||||
match &= !criteria.DateRanked.HasFilter || (beatmap.BeatmapSet?.DateRanked != null && criteria.DateRanked.IsInRange(beatmap.BeatmapSet.DateRanked.Value));
|
||||
match &= !criteria.DateSubmitted.HasFilter || (beatmap.BeatmapSet?.DateSubmitted != null && criteria.DateSubmitted.IsInRange(beatmap.BeatmapSet.DateSubmitted.Value));
|
||||
match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(beatmap.BPM);
|
||||
|
||||
match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(beatmap.BeatDivisor);
|
||||
match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(beatmap.Status);
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(beatmap.Metadata.Author.Username);
|
||||
match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(beatmap.Metadata.Artist) ||
|
||||
criteria.Artist.Matches(beatmap.Metadata.ArtistUnicode);
|
||||
match &= !criteria.Title.HasFilter || criteria.Title.Matches(beatmap.Metadata.Title) ||
|
||||
criteria.Title.Matches(beatmap.Metadata.TitleUnicode);
|
||||
match &= !criteria.DifficultyName.HasFilter || criteria.DifficultyName.Matches(beatmap.DifficultyName);
|
||||
match &= !criteria.Source.HasFilter || criteria.Source.Matches(beatmap.Metadata.Source);
|
||||
match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(beatmap.StarRating);
|
||||
|
||||
if (!match) return false;
|
||||
|
||||
match &= criteria.CollectionBeatmapMD5Hashes?.Contains(beatmap.MD5Hash) ?? true;
|
||||
if (match && criteria.RulesetCriteria != null)
|
||||
match &= criteria.RulesetCriteria.Matches(beatmap, criteria);
|
||||
|
||||
if (match && criteria.HasOnlineID == true)
|
||||
match &= beatmap.OnlineID >= 0;
|
||||
|
||||
if (match && criteria.BeatmapSetId != null)
|
||||
match &= criteria.BeatmapSetId == beatmap.BeatmapSet?.OnlineID;
|
||||
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user