mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 15:22:55 +08:00
Merge pull request #27635 from vladfrangu/feat/support-filtering-for-multiple-types
Support filtering for multiple statuses when searching beatmaps in the map picker
This commit is contained in:
commit
d0ee0cc085
@ -256,8 +256,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
const string query = "status=r";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min);
|
||||
Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max);
|
||||
Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -268,10 +268,71 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim());
|
||||
Assert.AreEqual(4, filterCriteria.SearchTerms.Length);
|
||||
Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min);
|
||||
Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive);
|
||||
Assert.AreEqual(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max);
|
||||
Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive);
|
||||
Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyMultipleEqualityStatusQueries()
|
||||
{
|
||||
const string query = "status=ranked status=loved";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyEqualStatusQueryWithMultipleValues()
|
||||
{
|
||||
const string query = "status=ranked,loved";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Is.Not.Empty);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyRangeStatusMatches()
|
||||
{
|
||||
const string query = "status>=r";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Has.Count.EqualTo(4));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Ranked));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Approved));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Qualified));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyRangeStatusWithMultipleMatchesQuery()
|
||||
{
|
||||
const string query = "status>=r,l";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Is.EquivalentTo(Enum.GetValues<BeatmapOnlineStatus>()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyTwoRangeStatusQuery()
|
||||
{
|
||||
const string query = "status>r status<l";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Has.Count.EqualTo(2));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Approved));
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Qualified));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyRangeAndEqualStatusQuery()
|
||||
{
|
||||
const string query = "status>r status=loved";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Is.Not.Empty);
|
||||
Assert.That(filterCriteria.OnlineStatus.Values, Contains.Item(BeatmapOnlineStatus.Loved));
|
||||
}
|
||||
|
||||
[TestCase("creator")]
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select
|
||||
public OptionalRange<double> Length;
|
||||
public OptionalRange<double> BPM;
|
||||
public OptionalRange<int> BeatDivisor;
|
||||
public OptionalRange<BeatmapOnlineStatus> OnlineStatus;
|
||||
public OptionalSet<BeatmapOnlineStatus> OnlineStatus = new OptionalSet<BeatmapOnlineStatus>();
|
||||
public OptionalRange<DateTimeOffset> LastPlayed;
|
||||
public OptionalTextFilter Creator;
|
||||
public OptionalTextFilter Artist;
|
||||
@ -114,6 +114,23 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
public IRulesetFilterCriteria? RulesetCriteria { get; set; }
|
||||
|
||||
public readonly struct OptionalSet<T> : IEquatable<OptionalSet<T>>
|
||||
where T : struct, Enum
|
||||
{
|
||||
public bool HasFilter => true;
|
||||
|
||||
public bool IsInRange(T value) => Values.Contains(value);
|
||||
|
||||
public HashSet<T> Values { get; }
|
||||
|
||||
public OptionalSet()
|
||||
{
|
||||
Values = Enum.GetValues<T>().ToHashSet();
|
||||
}
|
||||
|
||||
public bool Equals(OptionalSet<T> other) => Values.SetEquals(other.Values);
|
||||
}
|
||||
|
||||
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
||||
where T : struct
|
||||
{
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select
|
||||
return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt);
|
||||
|
||||
case "status":
|
||||
return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, tryParseEnum);
|
||||
return TryUpdateCriteriaSet(ref criteria.OnlineStatus, op, value);
|
||||
|
||||
case "creator":
|
||||
case "author":
|
||||
@ -300,6 +300,75 @@ namespace osu.Game.Screens.Select
|
||||
where T : struct
|
||||
=> parseFunction.Invoke(val, out var converted) && tryUpdateCriteriaRange(ref range, op, converted);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse a keyword filter of type <typeparamref name="T"/>,
|
||||
/// from the specified <paramref name="op"/> and <paramref name="filterValue"/>.
|
||||
/// If <paramref name="filterValue"/> can be parsed successfully, the function returns <c>true</c>
|
||||
/// and the resulting range constraint is stored into the <paramref name="range"/>'s expected values.
|
||||
/// </summary>
|
||||
/// <param name="range">The <see cref="FilterCriteria.OptionalSet{T}"/> to store the parsed data into, if successful.</param>
|
||||
/// <param name="op">The operator for the keyword filter.</param>
|
||||
/// <param name="filterValue">The value of the keyword filter.</param>
|
||||
public static bool TryUpdateCriteriaSet<T>(ref FilterCriteria.OptionalSet<T> range, Operator op, string filterValue)
|
||||
where T : struct, Enum
|
||||
{
|
||||
var matchingValues = new HashSet<T>();
|
||||
|
||||
if (op == Operator.Equal && filterValue.Contains(','))
|
||||
{
|
||||
string[] splitValues = filterValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
foreach (string splitValue in splitValues)
|
||||
{
|
||||
if (!tryParseEnum<T>(splitValue, out var parsedValue))
|
||||
return false;
|
||||
|
||||
matchingValues.Add(parsedValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!tryParseEnum<T>(filterValue, out var pivotValue))
|
||||
return false;
|
||||
|
||||
var allDefinedValues = Enum.GetValues<T>();
|
||||
|
||||
foreach (var val in allDefinedValues)
|
||||
{
|
||||
int compareResult = Comparer<T>.Default.Compare(val, pivotValue);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case Operator.Less:
|
||||
if (compareResult < 0) matchingValues.Add(val);
|
||||
break;
|
||||
|
||||
case Operator.LessOrEqual:
|
||||
if (compareResult <= 0) matchingValues.Add(val);
|
||||
break;
|
||||
|
||||
case Operator.Equal:
|
||||
if (compareResult == 0) matchingValues.Add(val);
|
||||
break;
|
||||
|
||||
case Operator.GreaterOrEqual:
|
||||
if (compareResult >= 0) matchingValues.Add(val);
|
||||
break;
|
||||
|
||||
case Operator.Greater:
|
||||
if (compareResult > 0) matchingValues.Add(val);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
range.Values.IntersectWith(matchingValues);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool tryUpdateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, Operator op, T value)
|
||||
where T : struct
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user