From 2bbf4ca4b59abf2a0e87b7c2481d922bacd773b5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 20 Sep 2019 18:50:50 +0900 Subject: [PATCH 01/39] Update LabelledTextBox to use LabelledComponent --- .../UserInterface/TestSceneLabelledTextBox.cs | 32 ++++- .../LabelledComponents/LabelledTextBox.cs | 117 +++--------------- 2 files changed, 51 insertions(+), 98 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 395905a30d..f9a5369576 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -19,6 +19,36 @@ namespace osu.Game.Tests.Visual.UserInterface typeof(LabelledTextBox), }; + [TestCase(false)] + [TestCase(true)] + public void TestTextBox(bool hasDescription) => createTextBox(hasDescription); + + private void createTextBox(bool hasDescription = false) + { + AddStep("create component", () => + { + LabelledComponent component; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Label = "Testing text", + PlaceholderText = "This is definitely working as intended", + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + }); + } + [BackgroundDependencyLoader] private void load() { @@ -32,7 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, - LabelText = "Testing text", + Label = "Testing text", PlaceholderText = "This is definitely working as intended", } }; diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 1c53fc7088..992371fedf 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -3,127 +3,50 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { - public class LabelledTextBox : CompositeDrawable + public class LabelledTextBox : LabelledComponent { - private const float label_container_width = 150; - private const float corner_radius = 15; - private const float default_height = 40; - private const float default_label_left_padding = 15; - private const float default_label_top_padding = 12; - private const float default_label_text_size = 16; - public event TextBox.OnCommitHandler OnCommit; + protected new OsuTextBox Component => (OsuTextBox)base.Component; + + public LabelledTextBox() + : base(false) + { + } + public bool ReadOnly { - get => textBox.ReadOnly; - set => textBox.ReadOnly = value; - } - - public string LabelText - { - get => label.Text; - set => label.Text = value; - } - - public float LabelTextSize - { - get => label.Font.Size; - set => label.Font = label.Font.With(size: value); + set => Component.ReadOnly = value; } public string PlaceholderText { - get => textBox.PlaceholderText; - set => textBox.PlaceholderText = value; + set => Component.PlaceholderText = value; } public string Text { - get => textBox.Text; - set => textBox.Text = value; - } - - public Color4 LabelTextColour - { - get => label.Colour; - set => label.Colour = value; - } - - private readonly OsuTextBox textBox; - private readonly OsuSpriteText label; - - public LabelledTextBox() - { - RelativeSizeAxes = Axes.X; - Height = default_height; - CornerRadius = corner_radius; - Masking = true; - - InternalChild = new Container - { - RelativeSizeAxes = Axes.Both, - CornerRadius = corner_radius, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new GridContainer - { - RelativeSizeAxes = Axes.X, - Height = default_height, - Content = new[] - { - new Drawable[] - { - label = new OsuSpriteText - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Padding = new MarginPadding { Left = default_label_left_padding, Top = default_label_top_padding }, - Colour = Color4.White, - Font = OsuFont.GetFont(size: default_label_text_size, weight: FontWeight.Bold), - }, - textBox = new OsuTextBox - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - RelativeSizeAxes = Axes.Both, - Height = 1, - CornerRadius = corner_radius, - }, - }, - }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, label_container_width), - new Dimension() - } - } - } - }; - - textBox.OnCommit += OnCommit; + set => Component.Text = value; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - textBox.BorderColour = colours.Blue; + Component.BorderColour = colours.Blue; } + + protected override Drawable CreateComponent() => new OsuTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + CornerRadius = CORNER_RADIUS, + }.With(t => t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText)); } } From 33c51d5178fc5191d143fd77407042e5dc6b6687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 20:55:30 +0200 Subject: [PATCH 02/39] Extract parsing filter queries to class For the sake of testability without having to spin up visual tests, extract methods related to parsing filter queries from FilterControl to a static FilterQueryParser class. --- osu.Game/Screens/Select/FilterControl.cs | 131 +----------------- osu.Game/Screens/Select/FilterQueryParser.cs | 138 +++++++++++++++++++ 2 files changed, 139 insertions(+), 130 deletions(-) create mode 100644 osu.Game/Screens/Select/FilterQueryParser.cs diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e3c23f7e22..91f1ca0307 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -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(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\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(value, true, out var statusValue): - updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); - break; - } - - query = query.Replace(match.ToString(), ""); - } - } - - private void updateCriteriaRange(ref FilterCriteria.OptionalRange 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 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(ref FilterCriteria.OptionalRange 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; - } - } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs new file mode 100644 index 0000000000..4e2b591fc9 --- /dev/null +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -0,0 +1,138 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +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(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\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; + + 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(value, true, out var statusValue): + updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); + break; + } + + query = query.Replace(match.ToString(), ""); + } + + criteria.SearchText = query; + } + + private static void updateCriteriaRange(ref FilterCriteria.OptionalRange 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 static void updateCriteriaRange(ref FilterCriteria.OptionalRange 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 static void updateCriteriaRange(ref FilterCriteria.OptionalRange 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; + } + } + } +} From dddd94684bd233e1377211d086e0f47c736fb934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 21:34:38 +0200 Subject: [PATCH 03/39] Split out lower and upper interval inclusivity A single IsInclusive field causes unexpected issues when trying to formulate a half-open interval query. Split out IsInclusive into two fields, Is{Lower,Upper}Inclusive and update usages accordingly. --- osu.Game/Screens/Select/FilterCriteria.cs | 10 ++++++---- osu.Game/Screens/Select/FilterQueryParser.cs | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index a3fa1b10ca..97a7f12724 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select if (comparison < 0) return false; - if (comparison == 0 && !IsInclusive) + if (comparison == 0 && !IsLowerInclusive) return false; } @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Select if (comparison > 0) return false; - if (comparison == 0 && !IsInclusive) + if (comparison == 0 && !IsUpperInclusive) return false; } @@ -73,12 +73,14 @@ namespace osu.Game.Screens.Select public T? Min; public T? Max; - public bool IsInclusive; + public bool IsLowerInclusive; + public bool IsUpperInclusive; public bool Equals(OptionalRange other) => Min.Equals(other.Min) && Max.Equals(other.Max) - && IsInclusive.Equals(other.IsInclusive); + && IsLowerInclusive.Equals(other.IsLowerInclusive) + && IsUpperInclusive.Equals(other.IsUpperInclusive); } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 4e2b591fc9..800f1afd03 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -106,30 +106,30 @@ namespace osu.Game.Screens.Select case "=": case ":": - range.IsInclusive = true; + range.IsLowerInclusive = range.IsUpperInclusive = true; range.Min = value; range.Max = value; break; case ">": - range.IsInclusive = false; + range.IsLowerInclusive = false; range.Min = value; break; case ">=": case ">:": - range.IsInclusive = true; + range.IsLowerInclusive = true; range.Min = value; break; case "<": - range.IsInclusive = false; + range.IsUpperInclusive = false; range.Max = value; break; case "<=": case "<:": - range.IsInclusive = true; + range.IsUpperInclusive = true; range.Max = value; break; } From f5f5094611257f50fc778becf402105868e9757f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 22:10:46 +0200 Subject: [PATCH 04/39] Take culture into account when parsing filters Culture was not taken into account when parsing filters, which meant that in cultures that use the comma (,) as a decimal delimiter, it would conflict with the comma used to delimit search criteria. To remove any ambiguity, introduce local helper functions that allow the decimal point to be utilised, using the invariant culture. This also matches stable behaviour. The decision to not reuse osu.Game.Beatmaps.Formats.Parsing was deliberate due to differing semantics (it's not really sane to throw exceptions on receiving user-facing input). --- osu.Game/Screens/Select/FilterQueryParser.cs | 24 ++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 800f1afd03..d6d19c8650 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -2,6 +2,7 @@ // 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; @@ -23,27 +24,27 @@ namespace osu.Game.Screens.Select switch (key) { - case "stars" when float.TryParse(value, out var stars): + case "stars" when parseFloatWithPoint(value, out var stars): updateCriteriaRange(ref criteria.StarDifficulty, op, stars); break; - case "ar" when float.TryParse(value, out var ar): + case "ar" when parseFloatWithPoint(value, out var ar): updateCriteriaRange(ref criteria.ApproachRate, op, ar); break; - case "dr" when float.TryParse(value, out var dr): + case "dr" when parseFloatWithPoint(value, out var dr): updateCriteriaRange(ref criteria.DrainRate, op, dr); break; - case "cs" when float.TryParse(value, out var cs): + case "cs" when parseFloatWithPoint(value, out var cs): updateCriteriaRange(ref criteria.CircleSize, op, cs); break; - case "bpm" when double.TryParse(value, out var bpm): + case "bpm" when parseDoubleWithPoint(value, out var bpm): updateCriteriaRange(ref criteria.BPM, op, bpm); break; - case "length" when double.TryParse(value.TrimEnd('m', 's', 'h'), out var length): + case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): var scale = value.EndsWith("ms") ? 1 : value.EndsWith("s") ? 1000 : @@ -53,7 +54,7 @@ namespace osu.Game.Screens.Select updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); break; - case "divisor" when int.TryParse(value, out var divisor): + case "divisor" when parseInt(value, out var divisor): updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); break; @@ -68,6 +69,15 @@ namespace osu.Game.Screens.Select criteria.SearchText = query; } + 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 updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { updateCriteriaRange(ref range, op, value); From d11d932a8747273f56f4128dbcf8b31bd6a329c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 22:19:45 +0200 Subject: [PATCH 05/39] Add filter parsing tests Introduce unit tests covering parsing for the originally introduced filtering features. The introduced improvements (lower and upper interval and decimal point support) also tested. --- .../Filtering/FilterQueryParserTest.cs | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs new file mode 100644 index 0000000000..f98ad1fc43 --- /dev/null +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -0,0 +1,129 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.NonVisual.Filtering +{ + [TestFixture] + public class FilterQueryParserTest + { + [Test] + public void TestApplyQueriesBareWords() + { + const string query = "looking for a beatmap"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("looking for a beatmap", filterCriteria.SearchText); + Assert.AreEqual(4, filterCriteria.SearchTerms.Length); + } + + [Test] + public void TestApplyStarQueries() + { + const string query = "stars<4 easy"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(4.0f, filterCriteria.StarDifficulty.Max); + Assert.IsFalse(filterCriteria.StarDifficulty.IsUpperInclusive); + Assert.IsNull(filterCriteria.StarDifficulty.Min); + } + + [Test] + public void TestApplyApproachRateQueries() + { + const string query = "ar>=9 difficult"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("difficult", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(9.0f, filterCriteria.ApproachRate.Min); + Assert.IsTrue(filterCriteria.ApproachRate.IsLowerInclusive); + Assert.IsNull(filterCriteria.ApproachRate.Max); + } + + [Test] + public void TestApplyDrainRateQueries() + { + const string query = "dr>2 quite specific dr<:6"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); + Assert.AreEqual(2, filterCriteria.SearchTerms.Length); + Assert.AreEqual(2.0f, filterCriteria.DrainRate.Min); + Assert.IsFalse(filterCriteria.DrainRate.IsLowerInclusive); + Assert.AreEqual(6.0f, filterCriteria.DrainRate.Max); + Assert.IsTrue(filterCriteria.DrainRate.IsUpperInclusive); + } + + [Test] + public void TestApplyBPMQueries() + { + const string query = "bpm>:200 gotta go fast"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim()); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + Assert.AreEqual(200d, filterCriteria.BPM.Min); + Assert.IsTrue(filterCriteria.BPM.IsLowerInclusive); + Assert.IsNull(filterCriteria.BPM.Max); + } + + private static object[] lengthQueryExamples = + { + new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) }, + new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) }, + new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) }, + new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) }, + new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) }, + }; + + [Test] + [TestCaseSource(nameof(lengthQueryExamples))] + public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale) + { + string query = $"length={lengthQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("time", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); + Assert.IsTrue(filterCriteria.Length.IsLowerInclusive); + Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); + Assert.IsTrue(filterCriteria.Length.IsUpperInclusive); + } + + [Test] + public void TestApplyDivisorQueries() + { + const string query = "that's a time signature alright! divisor:12"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("that's a time signature alright!", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual(12, filterCriteria.BeatDivisor.Min); + Assert.IsTrue(filterCriteria.BeatDivisor.IsLowerInclusive); + Assert.AreEqual(12, filterCriteria.BeatDivisor.Max); + Assert.IsTrue(filterCriteria.BeatDivisor.IsUpperInclusive); + } + + [Test] + public void TestApplyStatusQueries() + { + const string query = "I want the pp status=ranked"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("I want the pp", filterCriteria.SearchText.Trim()); + Assert.AreEqual(4, filterCriteria.SearchTerms.Length); + Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Min); + Assert.IsTrue(filterCriteria.OnlineStatus.IsLowerInclusive); + Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); + Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); + } + } +} From 41569fd2b63f82aa5a19f4f026120d0d2f2717ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 22:48:30 +0200 Subject: [PATCH 06/39] Add filter evaluating unit tests Introduce unit tests covering the actual evaluation of filters for beatmaps. Partially covers most scenarios. --- .../NonVisual/Filtering/FilterMatchingTest.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs new file mode 100644 index 0000000000..24e735310d --- /dev/null +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -0,0 +1,136 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; + +namespace osu.Game.Tests.NonVisual.Filtering +{ + [TestFixture] + public class FilterMatchingTest + { + private readonly BeatmapInfo exampleBeatmapInfo = new BeatmapInfo + { + Ruleset = new RulesetInfo { ID = 5 }, + StarDifficulty = 4.0d, + BaseDifficulty = new BeatmapDifficulty + { + ApproachRate = 5.0f, + DrainRate = 3.0f, + CircleSize = 2.0f, + }, + Metadata = new BeatmapMetadata + { + Artist = "The Artist", + ArtistUnicode = "The Artist", + Title = "Title goes here", + TitleUnicode = "Title goes here", + AuthorString = "Author", + Source = "unit tests", + Tags = "look for tags too", + }, + Version = "version as well", + Length = 2500, + BPM = 160, + BeatDivisor = 12, + Status = BeatmapSetOnlineStatus.Loved + }; + + [Test] + public void TestCriteriaMatchingNoRuleset() + { + var criteria = new FilterCriteria(); + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + + [Test] + public void TestCriteriaMatchingSpecificRuleset() + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsTrue(carouselItem.Filtered.Value); + } + + [Test] + public void TestCriteriaMatchingConvertedBeatmaps() + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.IsFalse(carouselItem.Filtered.Value); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestCriteriaMatchingRangeMin(bool inclusive) + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true, + ApproachRate = new FilterCriteria.OptionalRange + { + IsLowerInclusive = inclusive, + Min = 5.0f + } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(!inclusive, carouselItem.Filtered.Value); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestCriteriaMatchingRangeMax(bool inclusive) + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true, + BPM = new FilterCriteria.OptionalRange + { + IsUpperInclusive = inclusive, + Max = 160d + } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(!inclusive, carouselItem.Filtered.Value); + } + + [Test] + [TestCase("artist", false)] + [TestCase("artist title author", false)] + [TestCase("an artist", true)] + [TestCase("tags too", false)] + [TestCase("version", false)] + [TestCase("an auteur", true)] + public void TestCriteriaMatchingTerms(string terms, bool filtered) + { + var criteria = new FilterCriteria + { + Ruleset = new RulesetInfo { ID = 6 }, + AllowConvertedBeatmaps = true, + SearchText = terms + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + } +} From 51509f6be03523771e61269311086d6fdb49c7ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Sep 2019 23:06:20 +0200 Subject: [PATCH 07/39] Add filter steps to carousel visual test Just a couple of steps for added coverage in visual tests. Very on-the-surface, the unit tests are supposed to cover the gory details. --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 6669ec7da3..71399106f4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -242,6 +242,21 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); AddAssert("Selection is non-null", () => currentSelection != null); + + setSelected(1, 3); + AddStep("Apply a range filter", () => carousel.Filter(new FilterCriteria + { + SearchText = "#3", + StarDifficulty = new FilterCriteria.OptionalRange + { + Min = 2, + Max = 5.5, + IsLowerInclusive = true + } + }, false)); + checkSelected(3, 2); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); } /// From b262ba13cd5d2ea1c9a908bd542f7c28758299fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Sep 2019 23:16:23 +0200 Subject: [PATCH 08/39] Add creator= and artist= filters To match stable, add creator= and artist= filters to the beatmap carousel on song select screen. Contrary to stable, this implementation supports phrase queries with spaces within using double quotes. The quote handling is not entirely correct (can't nest), but quotes should rarely happen within names, and it is an edge case of an edge case - leaving best-effort as is. Test coverage also included. --- .../NonVisual/Filtering/FilterMatchingTest.cs | 71 ++++++++++++++++++- .../Filtering/FilterQueryParserTest.cs | 44 ++++++++++++ .../Select/Carousel/CarouselBeatmap.cs | 4 ++ osu.Game/Screens/Select/FilterCriteria.cs | 21 ++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 21 +++++- 5 files changed, 157 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 24e735310d..30686cb947 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestFixture] public class FilterMatchingTest { - private readonly BeatmapInfo exampleBeatmapInfo = new BeatmapInfo + private BeatmapInfo getExampleBeatmap() => new BeatmapInfo { Ruleset = new RulesetInfo { ID = 5 }, StarDifficulty = 4.0d, @@ -25,10 +25,10 @@ namespace osu.Game.Tests.NonVisual.Filtering Metadata = new BeatmapMetadata { Artist = "The Artist", - ArtistUnicode = "The Artist", + ArtistUnicode = "check unicode too", Title = "Title goes here", TitleUnicode = "Title goes here", - AuthorString = "Author", + AuthorString = "The Author", Source = "unit tests", Tags = "look for tags too", }, @@ -42,6 +42,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestCriteriaMatchingNoRuleset() { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria(); var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); carouselItem.Filter(criteria); @@ -51,6 +52,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestCriteriaMatchingSpecificRuleset() { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 } @@ -63,6 +65,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [Test] public void TestCriteriaMatchingConvertedBeatmaps() { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -78,6 +81,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase(false)] public void TestCriteriaMatchingRangeMin(bool inclusive) { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -98,6 +102,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase(false)] public void TestCriteriaMatchingRangeMax(bool inclusive) { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -122,6 +127,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCase("an auteur", true)] public void TestCriteriaMatchingTerms(string terms, bool filtered) { + var exampleBeatmapInfo = getExampleBeatmap(); var criteria = new FilterCriteria { Ruleset = new RulesetInfo { ID = 6 }, @@ -132,5 +138,64 @@ namespace osu.Game.Tests.NonVisual.Filtering carouselItem.Filter(criteria); Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + + [Test] + [TestCase("", false)] + [TestCase("The", false)] + [TestCase("THE", false)] + [TestCase("author", false)] + [TestCase("the author", false)] + [TestCase("the author AND then something else", true)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingCreator(string creatorName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = creatorName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + + [Test] + [TestCase("", false)] + [TestCase("The", false)] + [TestCase("THE", false)] + [TestCase("artist", false)] + [TestCase("the artist", false)] + [TestCase("the artist AND then something else", true)] + [TestCase("unicode too", false)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingArtist(string artistName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + var criteria = new FilterCriteria + { + Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } + + [Test] + [TestCase("", false)] + [TestCase("artist", false)] + [TestCase("unknown", true)] + public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered) + { + var exampleBeatmapInfo = getExampleBeatmap(); + exampleBeatmapInfo.Metadata.ArtistUnicode = null; + + var criteria = new FilterCriteria + { + Artist = new FilterCriteria.OptionalTextFilter { SearchTerm = artistName } + }; + var carouselItem = new CarouselBeatmap(exampleBeatmapInfo); + carouselItem.Filter(criteria); + Assert.AreEqual(filtered, carouselItem.Filtered.Value); + } } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index f98ad1fc43..daab690a84 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -125,5 +125,49 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(BeatmapSetOnlineStatus.Ranked, filterCriteria.OnlineStatus.Max); Assert.IsTrue(filterCriteria.OnlineStatus.IsUpperInclusive); } + + [Test] + public void TestApplyCreatorQueries() + { + const string query = "beatmap specifically by creator=my_fav"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("beatmap specifically by", filterCriteria.SearchText.Trim()); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm); + } + + [Test] + public void TestApplyArtistQueries() + { + const string query = "find me songs by artist=singer please"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim()); + Assert.AreEqual(5, filterCriteria.SearchTerms.Length); + Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm); + } + + [Test] + public void TestApplyArtistQueriesWithSpaces() + { + const string query = "really like artist=\"name with space\" yes"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim()); + Assert.AreEqual(3, filterCriteria.SearchTerms.Length); + Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm); + } + + [Test] + public void TestApplyArtistQueriesOneDoubleQuote() + { + const string query = "weird artist=double\"quote"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("weird", filterCriteria.SearchText.Trim()); + Assert.AreEqual(1, filterCriteria.SearchTerms.Length); + Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm); + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9cc84c8bdd..6c3c9d20f3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -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 &= diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 97a7f12724..c2cbac905e 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Select public OptionalRange BPM; public OptionalRange BeatDivisor; public OptionalRange OnlineStatus; + public OptionalTextFilter Creator; + public OptionalTextFilter Artist; public string[] SearchTerms = Array.Empty(); @@ -82,5 +84,24 @@ namespace osu.Game.Screens.Select && IsLowerInclusive.Equals(other.IsLowerInclusive) && IsUpperInclusive.Equals(other.IsUpperInclusive); } + + public struct OptionalTextFilter : IEquatable + { + 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; + } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index d6d19c8650..b9281c5d6f 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select internal static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status)(?[=:><]+)(?\S*)", + @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -61,6 +61,14 @@ namespace osu.Game.Screens.Select case "status" when Enum.TryParse(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; } query = query.Replace(match.ToString(), ""); @@ -78,6 +86,17 @@ namespace osu.Game.Screens.Select 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 range, string op, float value, float tolerance = 0.05f) { updateCriteriaRange(ref range, op, value); From 70842f71f4b54484ea1ef22c19379950c2a01a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Sep 2019 00:11:13 +0200 Subject: [PATCH 09/39] Fix floating point handling in filter intervals Due to floating-point rounding and representation errors, filters could wrongly display results incongruous with the wedge display text (ie. a beatmap with the BPM of 139.99999 would be displayed as having 140 BPM and also pass the bpm<140 filter). Apply tolerance when parsing floating-point constraints. The tolerance chosen is half of what the UI displays for the particular values (so for example half of 0.1 for AR/DR/CS, 0.01 for stars, etc.) Tests updated accordingly. --- .../Filtering/FilterQueryParserTest.cs | 35 ++++++++---- osu.Game/Screens/Select/FilterQueryParser.cs | 56 ++++++++++++++++--- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index daab690a84..9869ddde41 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -21,6 +21,16 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(4, filterCriteria.SearchTerms.Length); } + /* + * The following tests have been written a bit strangely (they don't check exact + * bound equality with what the filter says). + * This is to account for floating-point arithmetic issues. + * For example, specifying a bpm<140 filter would previously match beatmaps with BPM + * of 139.99999, which would be displayed in the UI as 140. + * Due to this the tests check the last tick inside the range and the first tick + * outside of the range. + */ + [Test] public void TestApplyStarQueries() { @@ -29,8 +39,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("easy", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(4.0f, filterCriteria.StarDifficulty.Max); - Assert.IsFalse(filterCriteria.StarDifficulty.IsUpperInclusive); + Assert.IsNotNull(filterCriteria.StarDifficulty.Max); + Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d); + Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d); Assert.IsNull(filterCriteria.StarDifficulty.Min); } @@ -42,8 +53,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("difficult", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); - Assert.AreEqual(9.0f, filterCriteria.ApproachRate.Min); - Assert.IsTrue(filterCriteria.ApproachRate.IsLowerInclusive); + Assert.IsNotNull(filterCriteria.ApproachRate.Min); + Assert.Greater(filterCriteria.ApproachRate.Min, 8.9f); + Assert.Less(filterCriteria.ApproachRate.Min, 9.0f); Assert.IsNull(filterCriteria.ApproachRate.Max); } @@ -55,10 +67,10 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); Assert.AreEqual(2, filterCriteria.SearchTerms.Length); - Assert.AreEqual(2.0f, filterCriteria.DrainRate.Min); - Assert.IsFalse(filterCriteria.DrainRate.IsLowerInclusive); - Assert.AreEqual(6.0f, filterCriteria.DrainRate.Max); - Assert.IsTrue(filterCriteria.DrainRate.IsUpperInclusive); + Assert.Greater(filterCriteria.DrainRate.Min, 2.0f); + Assert.Less(filterCriteria.DrainRate.Min, 2.1f); + Assert.Greater(filterCriteria.DrainRate.Max, 6.0f); + Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } [Test] @@ -69,8 +81,9 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("gotta go fast", filterCriteria.SearchText.Trim()); Assert.AreEqual(3, filterCriteria.SearchTerms.Length); - Assert.AreEqual(200d, filterCriteria.BPM.Min); - Assert.IsTrue(filterCriteria.BPM.IsLowerInclusive); + Assert.IsNotNull(filterCriteria.BPM.Min); + Assert.Greater(filterCriteria.BPM.Min, 199.99d); + Assert.Less(filterCriteria.BPM.Min, 200.00d); Assert.IsNull(filterCriteria.BPM.Max); } @@ -93,9 +106,7 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("time", filterCriteria.SearchText.Trim()); Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.AreEqual(expectedLength.TotalMilliseconds - scale.TotalMilliseconds / 2.0, filterCriteria.Length.Min); - Assert.IsTrue(filterCriteria.Length.IsLowerInclusive); Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max); - Assert.IsTrue(filterCriteria.Length.IsUpperInclusive); } [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b9281c5d6f..3ee704201e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -25,23 +25,23 @@ namespace osu.Game.Screens.Select switch (key) { case "stars" when parseFloatWithPoint(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, 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); + updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); break; case "dr" when parseFloatWithPoint(value, out var dr): - updateCriteriaRange(ref criteria.DrainRate, op, 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); + updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); break; case "bpm" when parseDoubleWithPoint(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm); + updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); break; case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): @@ -99,29 +99,67 @@ namespace osu.Game.Screens.Select private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) { - updateCriteriaRange(ref range, op, value); - 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 range, string op, double value, double tolerance = 0.05) { - updateCriteriaRange(ref range, op, value); - 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; } } From fc1d49631a934437b24ccab3014aa31f2c06f579 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 20:30:58 +0900 Subject: [PATCH 10/39] Allow top-level menu key pressed to progress the osu! logo --- osu.Game/Screens/Menu/Button.cs | 7 ++++--- osu.Game/Screens/Menu/ButtonSystem.cs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 1bf25a2504..ffeadb96c7 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Menu { public event Action 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; diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 1a3e1213b4..0dee478a4c 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -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.PressedKeys.Contains(b.TriggerKey))) + { + logo.Click(); + return true; + } + } + + return base.OnKeyDown(e); + } + public bool OnPressed(GlobalAction action) { switch (action) From e5b14ce74de34d23ce42913a3c24152d75022866 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 21:42:32 +0900 Subject: [PATCH 11/39] Add null check for safety Co-Authored-By: Salman Ahmed --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 0dee478a4c..7ac6b5c696 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -187,7 +187,7 @@ namespace osu.Game.Screens.Menu { if (buttonsTopLevel.Any(b => e.PressedKeys.Contains(b.TriggerKey))) { - logo.Click(); + logo?.Click(); return true; } } From 3b52e7c72408555fbf6d42d988033ef30bac7f0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 02:10:04 +0900 Subject: [PATCH 12/39] Add boilerplate logic --- .../Screens/TestSceneSetupScreen.cs | 17 +++++++++++++++++ osu.Game.Tournament/Screens/SetupScreen.cs | 9 +++++++++ osu.Game.Tournament/TournamentSceneManager.cs | 2 ++ 3 files changed, 28 insertions(+) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs create mode 100644 osu.Game.Tournament/Screens/SetupScreen.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs new file mode 100644 index 0000000000..650b4c5412 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSetupScreen.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Tournament.Screens; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneSetupScreen : TournamentTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new SetupScreen()); + } + } +} diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs new file mode 100644 index 0000000000..a5e0e5927a --- /dev/null +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -0,0 +1,9 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tournament.Screens +{ + public class SetupScreen : TournamentScreen + { + } +} diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 4c255be463..b1384023d3 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -106,6 +106,8 @@ namespace osu.Game.Tournament Direction = FillDirection.Vertical, Children = new Drawable[] { + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, + new Container { RelativeSizeAxes = Axes.X, Height = 50 }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, From 47a89231ad3da90ca774fb524a75cd61c68a4bbd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Sep 2019 04:15:02 +0900 Subject: [PATCH 13/39] Read from (and allow reloading) IPC source --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 186 +++++++++++---------- osu.Game.Tournament/Screens/SetupScreen.cs | 15 ++ 2 files changed, 117 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 4fd858bd12..e05d96e098 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Platform.Windows; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; @@ -26,103 +27,120 @@ namespace osu.Game.Tournament.IPC [Resolved] protected RulesetStore Rulesets { get; private set; } + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private LadderInfo ladder { get; set; } + private int lastBeatmapId; + private ScheduledDelegate scheduled; + + public Storage Storage { get; private set; } [BackgroundDependencyLoader] - private void load(LadderInfo ladder, GameHost host) + private void load() { - StableStorage stable; + LocateStableStorage(); + } + + public Storage LocateStableStorage() + { + scheduled?.Cancel(); + + Storage = null; try { - stable = new StableStorage(host as DesktopGameHost); + Storage = new StableStorage(host as DesktopGameHost); + + const string file_ipc_filename = "ipc.txt"; + const string file_ipc_state_filename = "ipc-state.txt"; + const string file_ipc_scores_filename = "ipc-scores.txt"; + const string file_ipc_channel_filename = "ipc-channel.txt"; + + if (Storage.Exists(file_ipc_filename)) + scheduled = Scheduler.AddDelayed(delegate + { + try + { + using (var stream = Storage.GetStream(file_ipc_filename)) + using (var sr = new StreamReader(stream)) + { + var beatmapId = int.Parse(sr.ReadLine()); + var mods = int.Parse(sr.ReadLine()); + + if (lastBeatmapId != beatmapId) + { + lastBeatmapId = beatmapId; + + var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); + + if (existing != null) + Beatmap.Value = existing.BeatmapInfo; + else + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); + req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); + API.Queue(req); + } + } + + Mods.Value = (LegacyMods)mods; + } + } + catch + { + // file might be in use. + } + + try + { + using (var stream = Storage.GetStream(file_ipc_channel_filename)) + using (var sr = new StreamReader(stream)) + { + ChatChannel.Value = sr.ReadLine(); + } + } + catch (Exception) + { + // file might be in use. + } + + try + { + using (var stream = Storage.GetStream(file_ipc_state_filename)) + using (var sr = new StreamReader(stream)) + { + State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); + } + } + catch (Exception) + { + // file might be in use. + } + + try + { + using (var stream = Storage.GetStream(file_ipc_scores_filename)) + using (var sr = new StreamReader(stream)) + { + Score1.Value = int.Parse(sr.ReadLine()); + Score2.Value = int.Parse(sr.ReadLine()); + } + } + catch (Exception) + { + // file might be in use. + } + }, 250, true); } catch (Exception e) { Logger.Error(e, "Stable installation could not be found; disabling file based IPC"); - return; } - const string file_ipc_filename = "ipc.txt"; - const string file_ipc_state_filename = "ipc-state.txt"; - const string file_ipc_scores_filename = "ipc-scores.txt"; - const string file_ipc_channel_filename = "ipc-channel.txt"; - - if (stable.Exists(file_ipc_filename)) - Scheduler.AddDelayed(delegate - { - try - { - using (var stream = stable.GetStream(file_ipc_filename)) - using (var sr = new StreamReader(stream)) - { - var beatmapId = int.Parse(sr.ReadLine()); - var mods = int.Parse(sr.ReadLine()); - - if (lastBeatmapId != beatmapId) - { - lastBeatmapId = beatmapId; - - var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); - - if (existing != null) - Beatmap.Value = existing.BeatmapInfo; - else - { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); - req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); - API.Queue(req); - } - } - - Mods.Value = (LegacyMods)mods; - } - } - catch - { - // file might be in use. - } - - try - { - using (var stream = stable.GetStream(file_ipc_channel_filename)) - using (var sr = new StreamReader(stream)) - { - ChatChannel.Value = sr.ReadLine(); - } - } - catch (Exception) - { - // file might be in use. - } - - try - { - using (var stream = stable.GetStream(file_ipc_state_filename)) - using (var sr = new StreamReader(stream)) - { - State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); - } - } - catch (Exception) - { - // file might be in use. - } - - try - { - using (var stream = stable.GetStream(file_ipc_scores_filename)) - using (var sr = new StreamReader(stream)) - { - Score1.Value = int.Parse(sr.ReadLine()); - Score2.Value = int.Parse(sr.ReadLine()); - } - } - catch (Exception) - { - // file might be in use. - } - }, 250, true); + return Storage; } /// diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index a5e0e5927a..b75b0056ce 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -1,9 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Tournament.IPC; + namespace osu.Game.Tournament.Screens { public class SetupScreen : TournamentScreen { + [Resolved] + private MatchIPCInfo ipc { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new SpriteText + { + Text = (ipc as FileBasedIPC)?.Storage.GetFullPath(string.Empty) + }); + } } } From 96c0c80dc58622af64cba3b020b2434b60132bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Sep 2019 21:20:50 +0200 Subject: [PATCH 14/39] Factor out methods in FilterQueryParser Factor FilterQueryParser.ApplyQueries into shorter methods to reduce method complexity. --- osu.Game/Screens/Select/FilterQueryParser.cs | 102 ++++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 3ee704201e..ffe1258168 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -22,54 +22,7 @@ namespace osu.Game.Screens.Select var op = match.Groups["op"].Value; var value = match.Groups["value"].Value; - 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 = - 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 parseInt(value, out var divisor): - updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); - break; - - case "status" when Enum.TryParse(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; - } + parseKeywordCriteria(criteria, key, value, op); query = query.Replace(match.ToString(), ""); } @@ -77,6 +30,59 @@ namespace osu.Game.Screens.Select 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(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); From e07aa94fc8701f0a2807ef04cbca20d26f75566c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:22:50 +0900 Subject: [PATCH 15/39] Allow reloading ipc source --- osu.Game.Tournament/Screens/SetupScreen.cs | 84 ++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index b75b0056ce..992762431e 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -1,9 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; -using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; using osu.Game.Tournament.IPC; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Tournament.Screens { @@ -15,10 +22,79 @@ namespace osu.Game.Tournament.Screens [BackgroundDependencyLoader] private void load() { - AddInternal(new SpriteText + reload(); + } + + private void reload() + { + var fileBasedIpc = ipc as FileBasedIPC; + + InternalChildren = new Drawable[] { - Text = (ipc as FileBasedIPC)?.Storage.GetFullPath(string.Empty) - }); + new ActionableInfo + { + Label = "Current IPC source", + ButtonText = "Refresh", + Action = () => + { + fileBasedIpc?.LocateStableStorage(); + reload(); + }, + Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found", + Failing = fileBasedIpc?.Storage == null, + Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." + } + }; + } + + private class ActionableInfo : LabelledComponent + { + private OsuButton button; + + public ActionableInfo() + : base(true) + { + } + + public string ButtonText + { + set => button.Text = value; + } + + public string Value + { + set => valueText.Text = value; + } + + public bool Failing + { + set => valueText.Colour = value ? Color4.Red : Color4.White; + } + + public Action Action; + + private OsuSpriteText valueText; + + protected override Drawable CreateComponent() => new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + valueText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + button = new TriangleButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(100, 30), + Action = () => Action?.Invoke() + }, + } + }; } } } From b41ac543c5a4a4d31e783a4c802f80d25871a427 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:45:23 +0900 Subject: [PATCH 16/39] Allow changing logged in user --- osu.Game.Tournament/Screens/SetupScreen.cs | 44 ++++++++++++++++++- osu.Game.Tournament/TournamentSceneManager.cs | 1 + .../Sections/General/LoginSettings.cs | 4 +- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 992762431e..8ccb469b13 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; using osu.Game.Tournament.IPC; using osuTK; @@ -16,12 +18,29 @@ namespace osu.Game.Tournament.Screens { public class SetupScreen : TournamentScreen { + private FillFlowContainer fillFlow; + + private LoginOverlay loginOverlay; + [Resolved] private MatchIPCInfo ipc { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + [BackgroundDependencyLoader] private void load() { + InternalChild = fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + }; + + api.LocalUser.BindValueChanged(_ => Schedule(reload)); reload(); } @@ -29,7 +48,7 @@ namespace osu.Game.Tournament.Screens { var fileBasedIpc = ipc as FileBasedIPC; - InternalChildren = new Drawable[] + fillFlow.Children = new Drawable[] { new ActionableInfo { @@ -43,6 +62,29 @@ namespace osu.Game.Tournament.Screens Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.Storage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." + }, + new ActionableInfo + { + Label = "Current User", + ButtonText = "Change Login", + Action = () => + { + api.Logout(); + + if (loginOverlay == null) + { + AddInternal(loginOverlay = new LoginOverlay() + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }); + } + + loginOverlay.State.Value = Visibility.Visible; + }, + Value = api?.LocalUser.Value.Username, + Failing = api?.IsLoggedIn != true, + Description = "In order to access the API and display metadata, a login is required." } }; } diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index b1384023d3..58c6e3fee6 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -69,6 +69,7 @@ namespace osu.Game.Tournament RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new SetupScreen(), new ScheduleScreen(), new LadderScreen(), new LadderEditorScreen(), diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 66fec1ecf9..b02b1a5489 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.General api?.Register(this); } - public void APIStateChanged(IAPIProvider api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) => Schedule(() => { form = null; @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } if (form != null) GetContainingInputManager()?.ChangeFocus(form); - } + }); public override bool AcceptsFocus => true; From 4b7a42119119bbfdb03f7a458572a938714fdd30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:47:51 +0900 Subject: [PATCH 17/39] Set setup screen as default when opening --- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 58c6e3fee6..02ee1c8603 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tournament }, }; - SetScreen(typeof(ScheduleScreen)); + SetScreen(typeof(SetupScreen)); } public void SetScreen(Type screenType) From bafb429e9b6ae8ddaf120089f0bf6e845d7c3fc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Sep 2019 04:49:21 +0900 Subject: [PATCH 18/39] Don't show video background --- osu.Game.Tournament/Screens/SetupScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 8ccb469b13..1cb4917790 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens { - public class SetupScreen : TournamentScreen + public class SetupScreen : TournamentScreen, IProvideVideo { private FillFlowContainer fillFlow; @@ -73,7 +73,7 @@ namespace osu.Game.Tournament.Screens if (loginOverlay == null) { - AddInternal(loginOverlay = new LoginOverlay() + AddInternal(loginOverlay = new LoginOverlay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From ffbab2535856f37dacdb6f49f494aa1ed20f3ccf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 23 Sep 2019 16:12:43 +0300 Subject: [PATCH 19/39] Fix incorrect icon margin in ChangelogOverlay --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 11dc2049fd..05bf56bc33 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -15,6 +15,7 @@ using osu.Game.Users; using osuTK.Graphics; using osu.Framework.Allocation; using System.Net; +using osuTK; namespace osu.Game.Overlays.Changelog { @@ -67,21 +68,31 @@ namespace osu.Game.Overlays.Changelog foreach (APIChangelogEntry entry in categoryEntries) { - LinkFlowContainer title = new LinkFlowContainer + LinkFlowContainer title; + + Container titleContainer = new Container { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Vertical = 5 }, + Child = title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } }; var entryColour = entry.Major ? colours.YellowLight : Color4.White; - title.AddIcon(entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, t => + titleContainer.Add(new SpriteIcon { - t.Font = fontSmall; - t.Colour = entryColour; - t.Padding = new MarginPadding { Left = -17, Right = 5 }; + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(fontSmall.Size), + Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, + Colour = entryColour, + Margin = new MarginPadding { Right = 5 }, }); title.AddText(entry.Title, t => @@ -139,7 +150,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); - ChangelogEntries.Add(title); + ChangelogEntries.Add(titleContainer); if (!string.IsNullOrEmpty(entry.MessageHtml)) { From 5c4dfe0809c6865adf8f8daec0756935200f1ee0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 23 Sep 2019 17:05:19 +0300 Subject: [PATCH 20/39] Apply suggested change --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 05bf56bc33..bce1be5941 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -68,6 +68,8 @@ namespace osu.Game.Overlays.Changelog foreach (APIChangelogEntry entry in categoryEntries) { + var entryColour = entry.Major ? colours.YellowLight : Color4.White; + LinkFlowContainer title; Container titleContainer = new Container @@ -75,26 +77,26 @@ namespace osu.Game.Overlays.Changelog AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Vertical = 5 }, - Child = title = new LinkFlowContainer + Children = new Drawable[] { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(fontSmall.Size), + Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, + Colour = entryColour, + Margin = new MarginPadding { Right = 5 }, + }, + title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } } }; - var entryColour = entry.Major ? colours.YellowLight : Color4.White; - - titleContainer.Add(new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Size = new Vector2(fontSmall.Size), - Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, - Colour = entryColour, - Margin = new MarginPadding { Right = 5 }, - }); - title.AddText(entry.Title, t => { t.Font = fontLarge; From 53603b559176e4ddbc62e5e3e9d54f67785b2d62 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2019 20:15:51 +0000 Subject: [PATCH 21/39] Bump System.ComponentModel.Annotations from 4.5.0 to 4.6.0 Bumps [System.ComponentModel.Annotations](https://github.com/dotnet/corefx) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a27a94b8f9..a699217503 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,6 +30,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a6516e6d1b..7803ea1e49 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -123,7 +123,7 @@ - + From 78ce62b187798d8e194c0c89ea6ee3a29154bc3b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2019 20:29:36 +0000 Subject: [PATCH 22/39] Bump System.IO.Packaging from 4.5.0 to 4.6.0 Bumps [System.IO.Packaging](https://github.com/dotnet/corefx) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 538aaf2d7a..03b002add7 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,7 +26,7 @@ - + From 50dcb70342c22f97398e9c45b2f7c8576f52807d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2019 21:04:20 +0000 Subject: [PATCH 23/39] Bump Microsoft.Win32.Registry from 4.5.0 to 4.6.0 Bumps [Microsoft.Win32.Registry](https://github.com/dotnet/corefx) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/corefx/releases) - [Commits](https://github.com/dotnet/corefx/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Tournament/osu.Game.Tournament.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 538aaf2d7a..9e8bb431c0 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index 4790fcbcde..bddaff0a80 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -11,6 +11,6 @@ - + \ No newline at end of file From 4555ecc5e05d4c053e8a7822ca736c4328481e75 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 15:09:08 +0900 Subject: [PATCH 24/39] Check for exact key --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 7ac6b5c696..ed8e4c70f9 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Menu { if (State == ButtonSystemState.Initial) { - if (buttonsTopLevel.Any(b => e.PressedKeys.Contains(b.TriggerKey))) + if (buttonsTopLevel.Any(b => e.Key == b.TriggerKey)) { logo?.Click(); return true; From 6e619fbd71ad8b5654e60ed7850b05ee0865f1a1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2019 06:35:04 +0000 Subject: [PATCH 25/39] Bump ppy.osu.Framework from 2019.921.0 to 2019.924.0 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.921.0 to 2019.924.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.921.0...2019.924.0) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a699217503..83632f3d41 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7803ea1e49..29a12f3707 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From 50efb4414f87a78b3fda6595056b6851389740f7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2019 06:35:31 +0000 Subject: [PATCH 26/39] Bump ppy.osu.Framework.Android from 2019.921.0 to 2019.924.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.921.0 to 2019.924.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.921.0...2019.924.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index c57fc342ba..46fd5424df 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + From 1860c2f9cea07e13cbf8c81c4d23ba45f2732266 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 16:22:30 +0900 Subject: [PATCH 27/39] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c57fc342ba..46fd5424df 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a699217503..83632f3d41 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 7803ea1e49..30f1da362d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From df692b091c415ee0c9090d00698130368023626b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 18:22:02 +0900 Subject: [PATCH 28/39] Make LabelledComponent generic --- .../UserInterface/TestSceneLabelledComponent.cs | 8 ++++---- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../LabelledComponents/LabelledComponent.cs | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index 73e0191adb..a762d561c2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledComponent component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), }; component.Label = "a sample component"; @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent + private class PaddedLabelledComponent : LabelledComponent { public PaddedLabelledComponent() : base(true) @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent + private class NonPaddedLabelledComponent : LabelledComponent { public NonPaddedLabelledComponent() : base(false) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 1cb4917790..7a44e7a0e1 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent + private class ActionableInfo : LabelledComponent { private OsuButton button; diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs index 19e9c329d6..770065cb0e 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs @@ -11,7 +11,8 @@ using osuTK; namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { - public abstract class LabelledComponent : CompositeDrawable + public abstract class LabelledComponent : 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 /// /// The component that is being displayed. /// - protected readonly Drawable Component; + protected readonly T Component; private readonly OsuTextFlowContainer labelText; private readonly OsuTextFlowContainer descriptionText; /// - /// Creates a new . + /// Creates a new . /// - /// Whether the component should be padded or should be expanded to the bounds of this . + /// Whether the component should be padded or should be expanded to the bounds of this . 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. /// /// The component. - protected abstract Drawable CreateComponent(); + protected abstract T CreateComponent(); } } From 6b702eb6de7820dcb4245db7f0fa737731bdf3f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 24 Sep 2019 18:25:17 +0900 Subject: [PATCH 29/39] Update with generic labelledcomponent --- .../Visual/UserInterface/TestSceneLabelledTextBox.cs | 3 ++- .../Setup/Components/LabelledComponents/LabelledTextBox.cs | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index f9a5369576..5c5b1262a6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; namespace osu.Game.Tests.Visual.UserInterface @@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledComponent component; Child = new Container { diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index 992371fedf..67bacea9f8 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -9,12 +9,10 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents { - public class LabelledTextBox : LabelledComponent + public class LabelledTextBox : LabelledComponent { public event TextBox.OnCommitHandler OnCommit; - protected new OsuTextBox Component => (OsuTextBox)base.Component; - public LabelledTextBox() : base(false) { @@ -41,7 +39,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents Component.BorderColour = colours.Blue; } - protected override Drawable CreateComponent() => new OsuTextBox + protected override OsuTextBox CreateComponent() => new OsuTextBox { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 42b60417639455df5bb6d53000f011ce86e38ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 18:03:01 +0900 Subject: [PATCH 30/39] Move post-update notification logic to UpdateManager base class --- osu.Desktop/OsuGameDesktop.cs | 1 + osu.Desktop/Overlays/VersionManager.cs | 55 +-------------- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- .../Updater/SimpleUpdateManager.cs | 18 +++-- osu.Game/Updater/UpdateManager.cs | 67 +++++++++++++++++++ 5 files changed, 78 insertions(+), 65 deletions(-) rename {osu.Desktop => osu.Game}/Updater/SimpleUpdateManager.cs (87%) create mode 100644 osu.Game/Updater/UpdateManager.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 761f52f961..7725ee6451 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -17,6 +17,7 @@ using osu.Framework.Logging; using osu.Framework.Platform.Windows; using osu.Framework.Screens; using osu.Game.Screens.Menu; +using osu.Game.Updater; namespace osu.Desktop { diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 51e801c185..6eed46867a 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -8,11 +8,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; @@ -20,17 +17,9 @@ namespace osu.Desktop.Overlays { public class VersionManager : OverlayContainer { - private OsuConfigManager config; - private OsuGameBase game; - private NotificationOverlay notificationOverlay; - [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) + private void load(OsuColour colours, TextureStore textures, OsuGameBase game) { - notificationOverlay = notification; - this.config = config; - this.game = game; - AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; @@ -85,48 +74,6 @@ namespace osu.Desktop.Overlays }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - var version = game.Version; - var lastVersion = config.Get(OsuSetting.Version); - - if (game.IsDeployedBuild && version != lastVersion) - { - config.Set(OsuSetting.Version, version); - - // only show a notification if we've previously saved a version to the config file (ie. not the first run). - if (!string.IsNullOrEmpty(lastVersion)) - notificationOverlay.Post(new UpdateCompleteNotification(version)); - } - } - - private class UpdateCompleteNotification : SimpleNotification - { - private readonly string version; - - public UpdateCompleteNotification(string version) - { - this.version = version; - Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) - { - Icon = FontAwesome.Solid.CheckSquare; - IconBackgound.Colour = colours.BlueDark; - - Activated = delegate - { - notificationOverlay.Hide(); - changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); - return true; - }; - } - } - protected override void PopIn() { this.FadeIn(1400, Easing.OutQuint); diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index fa41c061b5..60b47a8b3a 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -20,7 +20,7 @@ using LogLevel = Splat.LogLevel; namespace osu.Desktop.Updater { - public class SquirrelUpdateManager : Component + public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { private UpdateManager updateManager; private NotificationOverlay notificationOverlay; diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs similarity index 87% rename from osu.Desktop/Updater/SimpleUpdateManager.cs rename to osu.Game/Updater/SimpleUpdateManager.cs index 5184791de1..eec27d3325 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -6,31 +6,25 @@ using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Network; using osu.Framework.Platform; -using osu.Game; -using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Desktop.Updater +namespace osu.Game.Updater { /// /// An update manager that shows notifications if a newer release is detected. /// Installation is left up to the user. /// - internal class SimpleUpdateManager : CompositeDrawable + public class SimpleUpdateManager : UpdateManager { - private NotificationOverlay notificationOverlay; private string version; private GameHost host; [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuGameBase game, GameHost host) + private void load(OsuGameBase game, GameHost host) { - notificationOverlay = notification; - this.host = host; version = game.Version; @@ -50,7 +44,7 @@ namespace osu.Desktop.Updater if (latest.TagName != version) { - notificationOverlay.Post(new SimpleNotification + Notifications.Post(new SimpleNotification { Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + "Click here to download the new version, which can be installed over the top of your existing installation", @@ -82,6 +76,10 @@ namespace osu.Desktop.Updater case RuntimeInfo.Platform.MacOsx: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; + + case RuntimeInfo.Platform.Android: + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".apk")); + break; } return bestAsset?.BrowserDownloadUrl ?? release.HtmlUrl; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs new file mode 100644 index 0000000000..e256cdbe45 --- /dev/null +++ b/osu.Game/Updater/UpdateManager.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Updater +{ + public abstract class UpdateManager : CompositeDrawable + { + [Resolved] + private OsuConfigManager config { get; set; } + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + protected NotificationOverlay Notifications { get; private set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var version = game.Version; + var lastVersion = config.Get(OsuSetting.Version); + + if (game.IsDeployedBuild && version != lastVersion) + { + config.Set(OsuSetting.Version, version); + + // only show a notification if we've previously saved a version to the config file (ie. not the first run). + if (!string.IsNullOrEmpty(lastVersion)) + Notifications.Post(new UpdateCompleteNotification(version)); + } + } + + private class UpdateCompleteNotification : SimpleNotification + { + private readonly string version; + + public UpdateCompleteNotification(string version) + { + this.version = version; + Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.CheckSquare; + IconBackgound.Colour = colours.BlueDark; + + Activated = delegate + { + notificationOverlay.Hide(); + changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); + return true; + }; + } + } + } +} From f493f1c71d2bee7242b6a4d552bc1ce0f3f05df7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 19:00:26 +0900 Subject: [PATCH 31/39] Move new components to v2 namespace --- .../Visual/UserInterface/TestSceneLabelledComponent.cs | 2 +- .../Visual/UserInterface/TestSceneLabelledTextBox.cs | 2 +- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../UserInterfaceV2}/LabelledComponent.cs | 3 +-- .../UserInterfaceV2}/LabelledTextBox.cs | 3 +-- 5 files changed, 5 insertions(+), 7 deletions(-) rename osu.Game/{Screens/Edit/Setup/Components/LabelledComponents => Graphics/UserInterfaceV2}/LabelledComponent.cs (98%) rename osu.Game/{Screens/Edit/Setup/Components/LabelledComponents => Graphics/UserInterfaceV2}/LabelledTextBox.cs (93%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index a762d561c2..700adad9cb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; +using osu.Game.Graphics.UserInterfaceV2; using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 5c5b1262a6..53a2bfabbc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 7a44e7a0e1..091a837745 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -7,9 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; -using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; using osu.Game.Tournament.IPC; using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs similarity index 98% rename from osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs rename to osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 770065cb0e..2e659825b7 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -5,11 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +namespace osu.Game.Graphics.UserInterfaceV2 { public abstract class LabelledComponent : CompositeDrawable where T : Drawable diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs similarity index 93% rename from osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs rename to osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 67bacea9f8..50d2a14482 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -4,10 +4,9 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents +namespace osu.Game.Graphics.UserInterfaceV2 { public class LabelledTextBox : LabelledComponent { From e78f134b90792a65e0b8e7d59236c32ea415f156 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Sep 2019 19:35:42 +0900 Subject: [PATCH 32/39] Mark configuration lookup test headless --- osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index bbcc4140a9..578030748b 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Skinning; using osu.Game.Tests.Visual; @@ -17,6 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Skins { [TestFixture] + [HeadlessTest] public class TestSceneSkinConfigurationLookup : OsuTestScene { private LegacySkin source1; From 4a59e3351edb6f5b9eb12c398e43bbe1c43be3a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 02:42:12 +0900 Subject: [PATCH 33/39] Update beatmap carousel tests code style Also fixes one issue I spotted in BeatmapCarousel related to incorrectly holding a selection after new sets are loaded. --- .../SongSelect/TestSceneBeatmapCarousel.cs | 98 +++++++++++-------- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 + 2 files changed, 62 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 71399106f4..16e873a84c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly Stack selectedSets = new Stack(); private readonly HashSet eagerSelectedIDs = new HashSet(); - private BeatmapInfo currentSelection; + private BeatmapInfo currentSelection => carousel.SelectedBeatmap; private const int set_count = 5; @@ -56,37 +56,26 @@ namespace osu.Game.Tests.Visual.SongSelect { RelativeSizeAxes = Axes.Both, }); - - List beatmapSets = new List(); - - for (int i = 1; i <= set_count; i++) - beatmapSets.Add(createTestBeatmapSet(i)); - - carousel.SelectionChanged = s => currentSelection = s; - - loadBeatmaps(beatmapSets); - - testTraversal(); - testFiltering(); - testRandom(); - testAddRemove(); - testSorting(); - - testRemoveAll(); - testEmptyTraversal(); - testHiding(); - testSelectingFilteredRuleset(); - testCarouselRootIsRandom(); } - private void loadBeatmaps(List beatmapSets) + private void loadBeatmaps(List beatmapSets = null) { + if (beatmapSets == null) + { + beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + } + bool changed = false; AddStep($"Load {beatmapSets.Count} Beatmaps", () => { + carousel.Filter(new FilterCriteria()); carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; }); + AddUntilStep("Wait for load", () => changed); } @@ -173,8 +162,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test keyboard traversal /// - private void testTraversal() + [Test] + public void TestTraversal() { + loadBeatmaps(); + advanceSelection(direction: 1, diff: false); checkSelected(1, 1); @@ -199,8 +191,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test filtering /// - private void testFiltering() + [Test] + public void TestFiltering() { + loadBeatmaps(); + // basic filtering setSelected(1, 1); @@ -262,8 +257,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test random non-repeating algorithm /// - private void testRandom() + [Test] + public void TestRandom() { + loadBeatmaps(); + setSelected(1, 1); nextRandom(); @@ -299,8 +297,11 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test adding and removing beatmap sets /// - private void testAddRemove() + [Test] + public void TestAddRemove() { + loadBeatmaps(); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1))); AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2))); @@ -322,16 +323,22 @@ namespace osu.Game.Tests.Visual.SongSelect /// /// Test sorting /// - private void testSorting() + [Test] + public void TestSorting() { + loadBeatmaps(); + AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } - private void testRemoveAll() + [Test] + public void TestRemoveAll() { + loadBeatmaps(); + setSelected(2, 1); AddAssert("Selection is non-null", () => currentSelection != null); @@ -353,8 +360,11 @@ namespace osu.Game.Tests.Visual.SongSelect checkNoSelection(); } - private void testEmptyTraversal() + [Test] + public void TestEmptyTraversal() { + loadBeatmaps(new List()); + advanceSelection(direction: 1, diff: false); checkNoSelection(); @@ -368,11 +378,18 @@ namespace osu.Game.Tests.Visual.SongSelect checkNoSelection(); } - private void testHiding() + [Test] + public void TestHiding() { - var hidingSet = createTestBeatmapSet(1); - hidingSet.Beatmaps[1].Hidden = true; - AddStep("Add set with diff 2 hidden", () => carousel.UpdateBeatmapSet(hidingSet)); + BeatmapSetInfo hidingSet = null; + + AddStep("Add set with diff 2 hidden", () => + { + hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; + carousel.UpdateBeatmapSet(hidingSet); + }); + setSelected(1, 1); checkVisibleItemCount(true, 2); @@ -402,7 +419,8 @@ namespace osu.Game.Tests.Visual.SongSelect } } - private void testSelectingFilteredRuleset() + [Test] + public void TestSelectingFilteredRuleset() { var testMixed = createTestBeatmapSet(set_count + 1); AddStep("add mixed ruleset beatmapset", () => @@ -437,14 +455,16 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle)); } - private void testCarouselRootIsRandom() + [Test] + public void TestCarouselRootIsRandom() { - List beatmapSets = new List(); + List manySets = new List(); for (int i = 1; i <= 50; i++) - beatmapSets.Add(createTestBeatmapSet(i)); + manySets.Add(createTestBeatmapSet(i)); + + loadBeatmaps(manySets); - loadBeatmaps(beatmapSets); advanceSelection(direction: 1, diff: false); checkNonmatchingFilter(); checkNonmatchingFilter(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 23c581c6f9..c3436ffd45 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -82,6 +82,9 @@ namespace osu.Game.Screens.Select var _ = newRoot.Drawables; root = newRoot; + if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet)) + selectedBeatmapSet = null; + scrollableContent.Clear(false); itemsCache.Invalidate(); scrollPositionCache.Invalidate(); From 56b460365b7cd5b96ba03ba7ca118fb8db6bb9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2019 20:34:09 +0200 Subject: [PATCH 34/39] Add bar line anchoring checks in mania test stage Add steps checking bar line anchoring in the mania Stage visual test to reproduce the regression in #6215 and prevent it from happening in the future. --- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index e7fd601abe..56401dd082 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -67,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre)); AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.TopCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.BottomCentre)); AddStep("flip direction", () => { @@ -76,10 +78,14 @@ namespace osu.Game.Rulesets.Mania.Tests AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre)); AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.BottomCentre)); + AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre)); } private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor); + private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor); + private void createNote() { foreach (var stage in stages) From 09864d7f0edb3a6164d2d384acc977c2e8795ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2019 21:42:40 +0200 Subject: [PATCH 35/39] Add bar line visual check in taiko playfield test Add a step checking alignment of a centre and a bar line in taiko playfield. Purely visual test without asserts. --- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index 3aa461e779..cbbf5b0c09 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Strong Rim", () => addRimHit(true)); AddStep("Add bar line", () => addBarLine(false)); AddStep("Add major bar line", () => addBarLine(true)); + AddStep("Add centre w/ bar line", () => + { + addCentreHit(false); + addBarLine(true); + }); AddStep("Height test 1", () => changePlayfieldSize(1)); AddStep("Height test 2", () => changePlayfieldSize(2)); AddStep("Height test 3", () => changePlayfieldSize(3)); From 9323df26a1cafa6fa90e316bfa659fcb5584fa13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Sep 2019 23:03:55 +0200 Subject: [PATCH 36/39] Decouple bar line hitobjects from generator Introduce an IBarLine interface, which together with generic constraints helps decouple BarLineGenerator from the actual hitobject types it creates. Thanks to this, all rulesets that want bar lines can provide an implementation of IBarLine that also derives from the base hitobject class. This allows DrawableBarLines in taiko and mania to be migrated back to DrawableTaikoHitObject and DrawableManiaHitObject base classes respectively. This in turn resolves #6215 without code duplication, since the missing anchoring application is now done in mania's DrawableBarLine through deriving from DrawableManiaHitObject. --- osu.Game.Rulesets.Mania/Objects/BarLine.cs | 12 ++++++++++ .../Objects/Drawables/DrawableBarLine.cs | 3 +-- .../UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 1 - osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 12 ++++++++++ .../UI/DrawableTaikoRuleset.cs | 2 +- osu.Game/Rulesets/Objects/BarLine.cs | 16 -------------- osu.Game/Rulesets/Objects/BarLineGenerator.cs | 7 +++--- osu.Game/Rulesets/Objects/IBarLine.cs | 22 +++++++++++++++++++ 9 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Objects/BarLine.cs create mode 100644 osu.Game.Rulesets.Taiko/Objects/BarLine.cs delete mode 100644 osu.Game/Rulesets/Objects/BarLine.cs create mode 100644 osu.Game/Rulesets/Objects/IBarLine.cs diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs new file mode 100644 index 0000000000..0981b028b2 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Objects +{ + public class BarLine : ManiaHitObject, IBarLine + { + public bool Major { get; set; } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index be21610525..56bc797c7f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -4,7 +4,6 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK.Graphics; @@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// Visualises a . Although this derives DrawableManiaHitObject, /// this does not handle input/sound like a normal hit object. /// - public class DrawableBarLine : DrawableHitObject + public class DrawableBarLine : DrawableManiaHitObject { /// /// Height of major bar line triangles. diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 29863fba2e..d371c1f7a8 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { - BarLines = new BarLineGenerator(Beatmap).BarLines; + BarLines = new BarLineGenerator(Beatmap).BarLines; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 12faa499ad..5ab07416a6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osuTK; diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs new file mode 100644 index 0000000000..2afbbc737c --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Taiko.Objects +{ + public class BarLine : TaikoHitObject, IBarLine + { + public bool Major { get; set; } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 5caa9e4626..fc109bf6a6 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); + new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); diff --git a/osu.Game/Rulesets/Objects/BarLine.cs b/osu.Game/Rulesets/Objects/BarLine.cs deleted file mode 100644 index a5c716e127..0000000000 --- a/osu.Game/Rulesets/Objects/BarLine.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Objects -{ - /// - /// A hit object representing the end of a bar. - /// - public class BarLine : HitObject - { - /// - /// Whether this barline is a prominent beat (based on time signature of beatmap). - /// - public bool Major; - } -} diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index ce571d7b17..4f9395435e 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -10,12 +10,13 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects { - public class BarLineGenerator + public class BarLineGenerator + where TBarLine : class, IBarLine, new() { /// /// The generated bar lines. /// - public readonly List BarLines = new List(); + public readonly List BarLines = new List(); /// /// Constructs and generates bar lines for provided beatmap. @@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Objects for (double t = currentTimingPoint.Time; Precision.DefinitelyBigger(endTime, t); t += barLength, currentBeat++) { - BarLines.Add(new BarLine + BarLines.Add(new TBarLine { StartTime = t, Major = currentBeat % (int)currentTimingPoint.TimeSignature == 0 diff --git a/osu.Game/Rulesets/Objects/IBarLine.cs b/osu.Game/Rulesets/Objects/IBarLine.cs new file mode 100644 index 0000000000..14df80e3b9 --- /dev/null +++ b/osu.Game/Rulesets/Objects/IBarLine.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Objects +{ + /// + /// Interface for bar line hitobjects. + /// Used to decouple bar line generation from ruleset-specific rendering/drawing hierarchies. + /// + public interface IBarLine + { + /// + /// The time position of the bar. + /// + double StartTime { set; } + + /// + /// Whether this bar line is a prominent beat (based on time signature of beatmap). + /// + bool Major { set; } + } +} From 8efab559c8d65138c2a38097970797957f396233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Sep 2019 01:13:42 +0200 Subject: [PATCH 37/39] Remove unused using directives --- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 1 - osu.Game.Rulesets.Mania/UI/ManiaStage.cs | 1 - .../Objects/Drawables/DrawableBarLineMajor.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 56401dd082..d5fd2808b8 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 98a4b7d0b6..a28de7ea58 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index f5b75a781b..4d3a1a3f8a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { From 2089f6fc42ed65d088712553def79a8078716b8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Sep 2019 14:28:35 +0900 Subject: [PATCH 38/39] Fix potential test fail case --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 16e873a84c..f12a613bf1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -381,14 +381,10 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHiding() { - BeatmapSetInfo hidingSet = null; + BeatmapSetInfo hidingSet = createTestBeatmapSet(1); + hidingSet.Beatmaps[1].Hidden = true; - AddStep("Add set with diff 2 hidden", () => - { - hidingSet = createTestBeatmapSet(1); - hidingSet.Beatmaps[1].Hidden = true; - carousel.UpdateBeatmapSet(hidingSet); - }); + loadBeatmaps(new List { hidingSet }); setSelected(1, 1); From 74b2e99247ab38d210d76aac8050aac8533057f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 25 Sep 2019 16:25:15 +0900 Subject: [PATCH 39/39] Fix invalid cursor trail parts being drawn --- .../UI/Cursor/CursorTrail.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index b32dfd483f..80291c002e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -40,9 +40,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < max_sprites; i++) { - // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node - // This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms - parts[i].InvalidationID = 1; + // -1 signals that the part is unusable, and should not be drawn + parts[i].InvalidationID = -1; } } @@ -112,7 +111,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < parts.Length; ++i) { parts[i].Time -= time; - ++parts[i].InvalidationID; + + if (parts[i].InvalidationID != -1) + ++parts[i].InvalidationID; } time = 0; @@ -205,8 +206,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public TrailDrawNode(CursorTrail source) : base(source) { - for (int i = 0; i < max_sprites; i++) - parts[i].InvalidationID = 0; } public override void ApplyState() @@ -218,11 +217,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor size = Source.partSize; time = Source.time; - for (int i = 0; i < Source.parts.Length; ++i) - { - if (Source.parts[i].InvalidationID > parts[i].InvalidationID) - parts[i] = Source.parts[i]; - } + Source.parts.CopyTo(parts, 0); } public override void Draw(Action vertexAction) @@ -234,6 +229,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < parts.Length; ++i) { + if (parts[i].InvalidationID == -1) + continue; + vertexBatch.DrawTime = parts[i].Time; Vector2 pos = parts[i].Position;