1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

feat: Support filtering for multiple statuses

This commit is contained in:
Vlad Frangu 2024-03-16 21:32:55 +02:00
parent 96ad0d4b54
commit 0a6960296e
No known key found for this signature in database
GPG Key ID: C856D3DE86CB4A11
3 changed files with 60 additions and 8 deletions

View File

@ -256,8 +256,9 @@ 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.IsNotNull(filterCriteria.OnlineStatus.Values);
Assert.IsNotEmpty(filterCriteria.OnlineStatus.Values!);
Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values);
}
[Test]
@ -268,10 +269,19 @@ 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.IsNotNull(filterCriteria.OnlineStatus.Values);
Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values);
}
[Test]
public void TestApplyStatusMatches()
{
const string query = "status=ranked status=loved";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.IsNotNull(filterCriteria.OnlineStatus.Values);
Assert.Contains(BeatmapOnlineStatus.Ranked, filterCriteria.OnlineStatus.Values);
Assert.Contains(BeatmapOnlineStatus.Loved, filterCriteria.OnlineStatus.Values);
}
[TestCase("creator")]

View File

@ -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 OptionalArray<BeatmapOnlineStatus> OnlineStatus;
public OptionalRange<DateTimeOffset> LastPlayed;
public OptionalTextFilter Creator;
public OptionalTextFilter Artist;
@ -114,6 +114,21 @@ namespace osu.Game.Screens.Select
public IRulesetFilterCriteria? RulesetCriteria { get; set; }
public struct OptionalArray<T> : IEquatable<OptionalArray<T>>
where T : struct
{
public bool HasFilter => Values?.Length > 0;
public bool IsInRange(T value)
{
return Values?.Contains(value) ?? false;
}
public T[]? Values;
public bool Equals(OptionalArray<T> other) => Values?.SequenceEqual(other.Values ?? Array.Empty<T>()) ?? false;
}
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
where T : struct
{

View File

@ -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 TryUpdateArrayRange(ref criteria.OnlineStatus, op, value, tryParseEnum);
case "creator":
case "author":
@ -300,6 +300,33 @@ 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="val"/>.
/// If <paramref name="val"/> can be parsed into <typeparamref name="T"/> using <paramref name="parseFunction"/>, 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.OptionalArray{T}"/> to store the parsed data into, if successful.</param>
/// <param name="op">The operator for the keyword filter. Currently, only <see cref="Operator.Equal"/> can be used.</param>
/// <param name="val">The value of the keyword filter.</param>
/// <param name="parseFunction">Function used to determine if <paramref name="val"/> can be converted to type <typeparamref name="T"/>.</param>
public static bool TryUpdateArrayRange<T>(ref FilterCriteria.OptionalArray<T> range, Operator op, string val, TryParseFunction<T> parseFunction)
where T : struct
=> parseFunction.Invoke(val, out var converted) && tryUpdateArrayRange(ref range, op, converted);
private static bool tryUpdateArrayRange<T>(ref FilterCriteria.OptionalArray<T> range, Operator op, T value)
where T : struct
{
if (op != Operator.Equal)
return false;
range.Values ??= Array.Empty<T>();
range.Values = range.Values.Append(value).ToArray();
return true;
}
private static bool tryUpdateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, Operator op, T value)
where T : struct
{