mirror of https://github.com/ppy/osu
Add query-based filter modes to song select search field
This commit is contained in:
parent
52fcd834ab
commit
92556db9cd
|
@ -301,6 +301,7 @@ private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
|
|||
|
||||
var ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
|
||||
beatmap.BeatmapInfo.Ruleset = ruleset;
|
||||
|
||||
// TODO: this should be done in a better place once we actually need to dynamically update it.
|
||||
beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0;
|
||||
beatmap.BeatmapInfo.Length = calculateLength(beatmap);
|
||||
|
|
|
@ -24,12 +24,26 @@ public override void Filter(FilterCriteria criteria)
|
|||
{
|
||||
base.Filter(criteria);
|
||||
|
||||
bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
|
||||
bool match =
|
||||
criteria.Ruleset == null ||
|
||||
Beatmap.RulesetID == criteria.Ruleset.ID ||
|
||||
(Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
|
||||
|
||||
foreach (var criteriaTerm in criteria.SearchTerms)
|
||||
match &=
|
||||
Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
|
||||
Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
|
||||
match &= criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty);
|
||||
match &= criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate);
|
||||
match &= criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate);
|
||||
match &= criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize);
|
||||
match &= criteria.Length.IsInRange(Beatmap.Length);
|
||||
match &= criteria.BPM.IsInRange(Beatmap.BPM);
|
||||
|
||||
match &= !criteria.BeatDivisor.HasValue || criteria.BeatDivisor == Beatmap.BeatDivisor;
|
||||
match &= !criteria.OnlineStatus.HasValue || criteria.OnlineStatus == Beatmap.Status;
|
||||
|
||||
if (match)
|
||||
foreach (var criteriaTerm in criteria.SearchTerms)
|
||||
match &=
|
||||
Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) ||
|
||||
Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
|
||||
|
||||
Filtered.Value = !match;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
|
@ -33,14 +35,24 @@ public class FilterControl : Container
|
|||
|
||||
private Bindable<GroupMode> groupMode;
|
||||
|
||||
public FilterCriteria CreateCriteria() => new FilterCriteria
|
||||
public FilterCriteria CreateCriteria()
|
||||
{
|
||||
Group = groupMode.Value,
|
||||
Sort = sortMode.Value,
|
||||
SearchText = searchTextBox.Text,
|
||||
AllowConvertedBeatmaps = showConverted.Value,
|
||||
Ruleset = ruleset.Value
|
||||
};
|
||||
var query = searchTextBox.Text;
|
||||
|
||||
var criteria = new FilterCriteria
|
||||
{
|
||||
Group = groupMode.Value,
|
||||
Sort = sortMode.Value,
|
||||
AllowConvertedBeatmaps = showConverted.Value,
|
||||
Ruleset = ruleset.Value
|
||||
};
|
||||
|
||||
applyQueries(criteria, ref query);
|
||||
|
||||
criteria.SearchText = query;
|
||||
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public Action Exit;
|
||||
|
||||
|
@ -169,5 +181,97 @@ private void load(OsuColour colours, IBindable<RulesetInfo> parentRuleset, OsuCo
|
|||
}
|
||||
|
||||
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
|
||||
|
||||
private static readonly Regex query_syntax_regex = new Regex(
|
||||
@"\b(?<key>stars|ar|dr|cs|divisor|length|objects|bpm|status)(?<op>[:><]+)(?<value>\S*)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private void applyQueries(FilterCriteria criteria, ref string query)
|
||||
{
|
||||
foreach (Match match in query_syntax_regex.Matches(query))
|
||||
{
|
||||
var key = match.Groups["key"].Value.ToLower();
|
||||
var op = match.Groups["op"].Value;
|
||||
var value = match.Groups["value"].Value;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "stars" when double.TryParse(value, out var stars):
|
||||
updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.5);
|
||||
break;
|
||||
|
||||
case "ar" when double.TryParse(value, out var ar):
|
||||
updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.3);
|
||||
break;
|
||||
|
||||
case "dr" when double.TryParse(value, out var dr):
|
||||
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.3);
|
||||
break;
|
||||
|
||||
case "cs" when double.TryParse(value, out var cs):
|
||||
updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.3);
|
||||
break;
|
||||
|
||||
case "bpm" when double.TryParse(value, out var bpm):
|
||||
updateCriteriaRange(ref criteria.BPM, op, bpm, 0.3);
|
||||
break;
|
||||
|
||||
case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length):
|
||||
var scale =
|
||||
value.EndsWith("ms") ? 1 :
|
||||
value.EndsWith("s") ? 1000 :
|
||||
value.EndsWith("m") ? 60000 :
|
||||
value.EndsWith("h") ? 3600000 : 1000;
|
||||
|
||||
updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
|
||||
break;
|
||||
|
||||
case "divisor" when op == ":" && int.TryParse(value, out var divisor):
|
||||
criteria.BeatDivisor = divisor;
|
||||
break;
|
||||
|
||||
case "status" when op == ":" && Enum.TryParse<BeatmapSetOnlineStatus>(value, ignoreCase: true, out var statusValue):
|
||||
criteria.OnlineStatus = statusValue;
|
||||
break;
|
||||
}
|
||||
|
||||
query = query.Remove(match.Index, match.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double equalityToleration = 0)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
|
||||
case ":":
|
||||
range.IsInclusive = true;
|
||||
range.Min = value - equalityToleration;
|
||||
range.Max = value + equalityToleration;
|
||||
break;
|
||||
|
||||
case ">":
|
||||
range.IsInclusive = false;
|
||||
range.Min = value;
|
||||
break;
|
||||
|
||||
case ">:":
|
||||
range.IsInclusive = true;
|
||||
range.Min = value;
|
||||
break;
|
||||
|
||||
case "<":
|
||||
range.IsInclusive = false;
|
||||
range.Max = value;
|
||||
break;
|
||||
|
||||
case "<:":
|
||||
range.IsInclusive = true;
|
||||
range.Max = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
|
||||
|
@ -13,6 +14,17 @@ public class FilterCriteria
|
|||
public GroupMode Group;
|
||||
public SortMode Sort;
|
||||
|
||||
public OptionalRange StarDifficulty;
|
||||
public OptionalRange ApproachRate;
|
||||
public OptionalRange DrainRate;
|
||||
public OptionalRange CircleSize;
|
||||
public OptionalRange Length;
|
||||
public OptionalRange BPM;
|
||||
|
||||
public int? BeatDivisor;
|
||||
|
||||
public BeatmapSetOnlineStatus? OnlineStatus;
|
||||
|
||||
public string[] SearchTerms = Array.Empty<string>();
|
||||
|
||||
public RulesetInfo Ruleset;
|
||||
|
@ -26,8 +38,27 @@ public string SearchText
|
|||
set
|
||||
{
|
||||
searchText = value;
|
||||
SearchTerms = searchText.Split(',', ' ', '!').Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
||||
SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public struct OptionalRange : IEquatable<OptionalRange>
|
||||
{
|
||||
public bool IsInRange(double value)
|
||||
{
|
||||
if (Min.HasValue && (IsInclusive ? value < Min.Value : value <= Min.Value))
|
||||
return false;
|
||||
if (Max.HasValue && (IsInclusive ? value > Max.Value : value >= Max.Value))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public double? Min;
|
||||
public double? Max;
|
||||
public bool IsInclusive;
|
||||
|
||||
public bool Equals(OptionalRange range) => Min == range.Min && Max == range.Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue