Merge branch 'generic-labelledcomponent' into labelled-textbox-improvements

This commit is contained in:
smoogipoo
2019-09-24 18:23:01 +09:00
21 changed files with 971 additions and 252 deletions

View File

@ -11,7 +11,8 @@ using osuTK;
namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents
{
public abstract class LabelledComponent : CompositeDrawable
public abstract class LabelledComponent<T> : CompositeDrawable
where T : Drawable
{
protected const float CONTENT_PADDING_VERTICAL = 10;
protected const float CONTENT_PADDING_HORIZONTAL = 15;
@ -20,15 +21,15 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents
/// <summary>
/// The component that is being displayed.
/// </summary>
protected readonly Drawable Component;
protected readonly T Component;
private readonly OsuTextFlowContainer labelText;
private readonly OsuTextFlowContainer descriptionText;
/// <summary>
/// Creates a new <see cref="LabelledComponent"/>.
/// Creates a new <see cref="LabelledComponent{T}"/>.
/// </summary>
/// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent"/>.</param>
/// <param name="padded">Whether the component should be padded or should be expanded to the bounds of this <see cref="LabelledComponent{T}"/>.</param>
protected LabelledComponent(bool padded)
{
RelativeSizeAxes = Axes.X;
@ -127,6 +128,6 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents
/// Creates the component that should be displayed.
/// </summary>
/// <returns>The component.</returns>
protected abstract Drawable CreateComponent();
protected abstract T CreateComponent();
}
}

View File

@ -31,6 +31,8 @@ namespace osu.Game.Screens.Menu
{
public event Action<ButtonState> StateChanged;
public readonly Key TriggerKey;
private readonly Container iconText;
private readonly Container box;
private readonly Box boxHoverLayer;
@ -43,7 +45,6 @@ namespace osu.Game.Screens.Menu
public ButtonSystemState VisibleState = ButtonSystemState.TopLevel;
private readonly Action clickAction;
private readonly Key triggerKey;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
@ -53,7 +54,7 @@ namespace osu.Game.Screens.Menu
{
this.sampleName = sampleName;
this.clickAction = clickAction;
this.triggerKey = triggerKey;
TriggerKey = triggerKey;
AutoSizeAxes = Axes.Both;
Alpha = 0;
@ -210,7 +211,7 @@ namespace osu.Game.Screens.Menu
if (e.Repeat || e.ControlPressed || e.ShiftPressed || e.AltPressed)
return false;
if (triggerKey == e.Key && triggerKey != Key.Unknown)
if (TriggerKey == e.Key && TriggerKey != Key.Unknown)
{
trigger();
return true;

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Framework.Threading;
@ -180,6 +181,20 @@ namespace osu.Game.Screens.Menu
State = ButtonSystemState.Initial;
}
protected override bool OnKeyDown(KeyDownEvent e)
{
if (State == ButtonSystemState.Initial)
{
if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey))
{
logo?.Click();
return true;
}
}
return base.OnKeyDown(e);
}
public bool OnPressed(GlobalAction action)
{
switch (action)

View File

@ -39,6 +39,10 @@ namespace osu.Game.Screens.Select.Carousel
match &= criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor);
match &= criteria.OnlineStatus.IsInRange(Beatmap.Status);
match &= criteria.Creator.Matches(Beatmap.Metadata.AuthorString);
match &= criteria.Artist.Matches(Beatmap.Metadata.Artist) ||
criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode);
if (match)
foreach (var criteriaTerm in criteria.SearchTerms)
match &=

View File

@ -16,8 +16,6 @@ using Container = osu.Framework.Graphics.Containers.Container;
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
{
@ -47,10 +45,7 @@ namespace osu.Game.Screens.Select
Ruleset = ruleset.Value
};
applyQueries(criteria, ref query);
criteria.SearchText = query;
FilterQueryParser.ApplyQueries(criteria, query);
return criteria;
}
@ -181,129 +176,5 @@ namespace osu.Game.Screens.Select
}
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 float.TryParse(value, out var stars):
updateCriteriaRange(ref criteria.StarDifficulty, op, stars);
break;
case "ar" when float.TryParse(value, out var ar):
updateCriteriaRange(ref criteria.ApproachRate, op, ar);
break;
case "dr" when float.TryParse(value, out var dr):
updateCriteriaRange(ref criteria.DrainRate, op, dr);
break;
case "cs" when float.TryParse(value, out var cs):
updateCriteriaRange(ref criteria.CircleSize, op, cs);
break;
case "bpm" when double.TryParse(value, out var bpm):
updateCriteriaRange(ref criteria.BPM, op, bpm);
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 int.TryParse(value, out var divisor):
updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
break;
case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue):
updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
break;
}
query = query.Replace(match.ToString(), "");
}
}
private void updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, string op, float value, float tolerance = 0.05f)
{
updateCriteriaRange(ref range, op, value);
switch (op)
{
case "=":
case ":":
range.Min = value - tolerance;
range.Max = value + tolerance;
break;
}
}
private void updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, string op, double value, double tolerance = 0.05)
{
updateCriteriaRange(ref range, op, value);
switch (op)
{
case "=":
case ":":
range.Min = value - tolerance;
range.Max = value + tolerance;
break;
}
}
private void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
where T : struct, IComparable
{
switch (op)
{
default:
return;
case "=":
case ":":
range.IsInclusive = true;
range.Min = value;
range.Max = value;
break;
case ">":
range.IsInclusive = false;
range.Min = value;
break;
case ">=":
case ">:":
range.IsInclusive = true;
range.Min = value;
break;
case "<":
range.IsInclusive = false;
range.Max = value;
break;
case "<=":
case "<:":
range.IsInclusive = true;
range.Max = value;
break;
}
}
}
}

View File

@ -23,6 +23,8 @@ namespace osu.Game.Screens.Select
public OptionalRange<double> BPM;
public OptionalRange<int> BeatDivisor;
public OptionalRange<BeatmapSetOnlineStatus> OnlineStatus;
public OptionalTextFilter Creator;
public OptionalTextFilter Artist;
public string[] SearchTerms = Array.Empty<string>();
@ -53,7 +55,7 @@ namespace osu.Game.Screens.Select
if (comparison < 0)
return false;
if (comparison == 0 && !IsInclusive)
if (comparison == 0 && !IsLowerInclusive)
return false;
}
@ -64,7 +66,7 @@ namespace osu.Game.Screens.Select
if (comparison > 0)
return false;
if (comparison == 0 && !IsInclusive)
if (comparison == 0 && !IsUpperInclusive)
return false;
}
@ -73,12 +75,33 @@ namespace osu.Game.Screens.Select
public T? Min;
public T? Max;
public bool IsInclusive;
public bool IsLowerInclusive;
public bool IsUpperInclusive;
public bool Equals(OptionalRange<T> other)
=> Min.Equals(other.Min)
&& Max.Equals(other.Max)
&& IsInclusive.Equals(other.IsInclusive);
&& IsLowerInclusive.Equals(other.IsLowerInclusive)
&& IsUpperInclusive.Equals(other.IsUpperInclusive);
}
public struct OptionalTextFilter : IEquatable<OptionalTextFilter>
{
public bool Matches(string value)
{
if (string.IsNullOrEmpty(SearchTerm))
return true;
// search term is guaranteed to be non-empty, so if the string we're comparing is empty, it's not matching
if (string.IsNullOrEmpty(value))
return false;
return value.IndexOf(SearchTerm, StringComparison.InvariantCultureIgnoreCase) >= 0;
}
public string SearchTerm;
public bool Equals(OptionalTextFilter other) => SearchTerm?.Equals(other.SearchTerm) ?? true;
}
}
}

View File

@ -0,0 +1,211 @@
// 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.Globalization;
using System.Text.RegularExpressions;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Select
{
internal static class FilterQueryParser
{
private static readonly Regex query_syntax_regex = new Regex(
@"\b(?<key>stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?<op>[=:><]+)(?<value>("".*"")|(\S*))",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static void ApplyQueries(FilterCriteria criteria, 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;
parseKeywordCriteria(criteria, key, value, op);
query = query.Replace(match.ToString(), "");
}
criteria.SearchText = query;
}
private static void parseKeywordCriteria(FilterCriteria criteria, string key, string value, string op)
{
switch (key)
{
case "stars" when parseFloatWithPoint(value, out var stars):
updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2);
break;
case "ar" when parseFloatWithPoint(value, out var ar):
updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2);
break;
case "dr" when parseFloatWithPoint(value, out var dr):
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
break;
case "cs" when parseFloatWithPoint(value, out var cs):
updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2);
break;
case "bpm" when parseDoubleWithPoint(value, out var bpm):
updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2);
break;
case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length):
var scale = getLengthScale(value);
updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
break;
case "divisor" when parseInt(value, out var divisor):
updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
break;
case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue):
updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
break;
case "creator":
updateCriteriaText(ref criteria.Creator, op, value);
break;
case "artist":
updateCriteriaText(ref criteria.Artist, op, value);
break;
}
}
private static int getLengthScale(string value) =>
value.EndsWith("ms") ? 1 :
value.EndsWith("s") ? 1000 :
value.EndsWith("m") ? 60000 :
value.EndsWith("h") ? 3600000 : 1000;
private static bool parseFloatWithPoint(string value, out float result) =>
float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result);
private static bool parseDoubleWithPoint(string value, out double result) =>
double.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result);
private static bool parseInt(string value, out int result) =>
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
private static void updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, string op, string value)
{
switch (op)
{
case "=":
case ":":
textFilter.SearchTerm = value.Trim('"');
break;
}
}
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, string op, float value, float tolerance = 0.05f)
{
switch (op)
{
default:
return;
case "=":
case ":":
range.Min = value - tolerance;
range.Max = value + tolerance;
break;
case ">":
range.Min = value + tolerance;
break;
case ">=":
case ">:":
range.Min = value - tolerance;
break;
case "<":
range.Max = value - tolerance;
break;
case "<=":
case "<:":
range.Max = value + tolerance;
break;
}
}
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, string op, double value, double tolerance = 0.05)
{
switch (op)
{
default:
return;
case "=":
case ":":
range.Min = value - tolerance;
range.Max = value + tolerance;
break;
case ">":
range.Min = value + tolerance;
break;
case ">=":
case ">:":
range.Min = value - tolerance;
break;
case "<":
range.Max = value - tolerance;
break;
case "<=":
case "<:":
range.Max = value + tolerance;
break;
}
}
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
where T : struct, IComparable
{
switch (op)
{
default:
return;
case "=":
case ":":
range.IsLowerInclusive = range.IsUpperInclusive = true;
range.Min = value;
range.Max = value;
break;
case ">":
range.IsLowerInclusive = false;
range.Min = value;
break;
case ">=":
case ">:":
range.IsLowerInclusive = true;
range.Min = value;
break;
case "<":
range.IsUpperInclusive = false;
range.Max = value;
break;
case "<=":
case "<:":
range.IsUpperInclusive = true;
range.Max = value;
break;
}
}
}
}