From 8115a4bb8fd9e2d53c40b8607c7ad99f0f62e9a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:28:32 +0900 Subject: [PATCH 01/18] Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index ff19dd874c..f1cb0731fe 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,6 +14,8 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { + LookupKeyBindings = _ => "unknown"; + LookupSkinName = _ => "unknown"; } } } From 657f2ebb9dbd355428bb6b48845b0edd83155e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:59:10 +0900 Subject: [PATCH 02/18] Restructure `OsuSliderBar` to allow for custom tooltips --- .../Graphics/UserInterface/OsuSliderBar.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 333ae4f832..17dce10cf6 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; protected set; } /// /// Whether to format the tooltip as a percentage or the actual value. @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - updateTooltipText(value); + TooltipText = GetTooltipText(value); } private void playSample(T value) @@ -203,28 +203,22 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private void updateTooltipText(T value) + protected virtual LocalisableString GetTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - else - { - double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - if (DisplayAsPercentage) - { - TooltipText = floatValue.ToString("0%"); - } - else - { - decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); - // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + if (DisplayAsPercentage) + return floatValue.ToString("0%"); - TooltipText = floatValue.ToString($"N{significantDigits}"); - } - } + decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + + return floatValue.ToString($"N{significantDigits}"); } protected override void UpdateAfterChildren() From cc4f89eef429160c4304841ef735a0b79d048820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:07:46 +0900 Subject: [PATCH 03/18] Add tooltip text for offset adjustment slider --- .../BeatmapOffsetControlStrings.cs | 12 +++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 28 ++++++++++++++++++- .../Play/PlayerSettings/PlayerSliderBar.cs | 12 ++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 7b2a9e50b2..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -29,6 +29,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play"); + /// + /// "(hit objects appear later)" + /// + public static LocalisableString HitObjectsAppearLater => new TranslatableString(getKey(@"hit_objects_appear_later"), @"(hit objects appear later)"); + + /// + /// "(hit objects appear earlier)" + /// + public static LocalisableString HitObjectsAppearEarlier => new TranslatableString(getKey(@"hit_objects_appear_earlier"), @"(hit objects appear earlier)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..201e431367 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -3,12 +3,14 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; @@ -71,7 +73,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Spacing = new Vector2(10), Children = new Drawable[] { - new PlayerSliderBar + new OffsetSliderBar { KeyboardStep = 5, LabelText = BeatmapOffsetControlStrings.BeatmapOffset, @@ -88,6 +90,30 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } + public class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected class CustomSliderBar : SliderBar + { + protected override LocalisableString GetTooltipText(double value) + { + return value == 0 + ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) + : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); + } + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearLater + : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + } + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 57ffe16f76..3f1a5bc0ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -15,13 +15,15 @@ namespace osu.Game.Screens.Play.PlayerSettings { public OsuSliderBar Bar => (OsuSliderBar)Control; - protected override Drawable CreateControl() => new SliderBar - { - RelativeSizeAxes = Axes.X - }; + protected override Drawable CreateControl() => new SliderBar(); - private class SliderBar : OsuSliderBar + protected class SliderBar : OsuSliderBar { + public SliderBar() + { + RelativeSizeAxes = Axes.X; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From 5dca0e3377fbb69667648709022db0440fe09394 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 21:31:56 +0900 Subject: [PATCH 04/18] Revert back to `private set` Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 17dce10cf6..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; protected set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. From c1c9482077655d9315803cd5239aa66c16604928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 00:14:24 +0900 Subject: [PATCH 05/18] Add note about how global audio offset is currently applied --- osu.Game/Configuration/OsuConfigManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..5f9cd0470c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -270,7 +270,13 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, ConfineMouseMode, + + /// + /// Globally applied audio offset. + /// This is added to the audio track's current time. Higher values will cause gameplay to occur earlier, relative to the audio track. + /// AudioOffset, + VolumeInactive, MenuMusic, MenuVoice, From e09dd7d8fe79ed1019f99b76946931db152eebc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:55:35 +0900 Subject: [PATCH 06/18] Fix calibrating offset from previous non-zero offset not applying adjustment correctly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 30 ++++++++++++++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 67f5db548b..4b079cbb2c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestDisplay() + public void TestCalibrationFromZero() { const double average_error = -4.5; @@ -70,5 +70,33 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + + /// + /// When a beatmap offset was already set, the calibration should take it into account. + /// + [Test] + public void TestCalibrationFromNonZero() + { + const double average_error = -4.5; + const double initial_offset = -2; + + AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + }; + }); + + AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); + + AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 201e431367..1d6d0acb76 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -53,6 +53,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private OsuColour colours { get; set; } = null!; private double lastPlayAverage; + private double lastPlayBeatmapOffset; private SettingsButton? useAverageButton; @@ -156,7 +157,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { @@ -213,6 +214,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } lastPlayAverage = average; + lastPlayBeatmapOffset = Current.Value; referenceScoreContainer.AddRange(new Drawable[] { @@ -225,7 +227,7 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = -lastPlayAverage + Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, }); } From 010fa7ed0185dcce7192c50abb972fb864ac1e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 13:09:19 +0900 Subject: [PATCH 07/18] Allow an offset to be shown on the timing distribution graph --- .../PlayerSettings/BeatmapOffsetControl.cs | 17 ++++++-- .../HitEventTimingDistributionGraph.cs | 42 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 1d6d0acb76..4af2de6409 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -54,6 +54,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private double lastPlayAverage; private double lastPlayBeatmapOffset; + private HitEventTimingDistributionGraph? lastPlayGraph; private SettingsButton? useAverageButton; @@ -109,8 +110,8 @@ namespace osu.Game.Screens.Play.PlayerSettings Debug.Assert(value != 0); return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearLater - : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; } } } @@ -149,6 +150,12 @@ namespace osu.Game.Screens.Play.PlayerSettings void updateOffset() { + // the last play graph is relative to the offset at the point of the last play, so we need to factor that out. + double adjustmentSinceLastPlay = lastPlayBeatmapOffset - Current.Value; + + // Negative is applied here because the play graph is considering a hit offset, not track (as we currently use for clocks). + lastPlayGraph?.UpdateOffset(-adjustmentSinceLastPlay); + // ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence. if (realmWriteTask?.IsCompleted == false) { @@ -157,7 +164,9 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); + { + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, adjustmentSinceLastPlay, Current.Precision / 2); + } realmWriteTask = realm.WriteAsync(r => { @@ -218,7 +227,7 @@ namespace osu.Game.Screens.Play.PlayerSettings referenceScoreContainer.AddRange(new Drawable[] { - new HitEventTimingDistributionGraph(hitEvents) + lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents) { RelativeSizeAxes = Axes.X, Height = 50, diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index b32b11c028..48c18deaec 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// private const float axis_points = 5; + /// + /// The currently displayed hit events. + /// private readonly IReadOnlyList hitEvents; /// @@ -51,24 +54,43 @@ namespace osu.Game.Screens.Ranking.Statistics this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } + private int[] bins; + private double binSize; + private double hitOffset; + [BackgroundDependencyLoader] private void load() { if (hitEvents == null || hitEvents.Count == 0) return; - int[] bins = new int[total_timing_distribution_bins]; + bins = new int[total_timing_distribution_bins]; - double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); + binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); // Prevent div-by-0 by enforcing a minimum bin size binSize = Math.Max(1, binSize); + Scheduler.AddOnce(updateDisplay); + } + + public void UpdateOffset(double hitOffset) + { + this.hitOffset = hitOffset; + Scheduler.AddOnce(updateDisplay); + } + + private void updateDisplay() + { bool roundUp = true; + Array.Clear(bins, 0, bins.Length); + foreach (var e in hitEvents) { - double binOffset = e.TimeOffset / binSize; + double time = e.TimeOffset + hitOffset; + + double binOffset = time / binSize; // .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display // purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket @@ -79,13 +101,23 @@ namespace osu.Game.Screens.Ranking.Statistics roundUp = !roundUp; } - bins[timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero)]++; + int index = timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero); + + // may be out of range when applying an offset. for such cases we can just drop the results. + if (index >= 0 && index < bins.Length) + bins[index]++; } int maxCount = bins.Max(); var bars = new Drawable[total_timing_distribution_bins]; + for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; + { + bars[i] = new Bar + { + Height = Math.Max(0.05f, (float)bins[i] / maxCount) + }; + } Container axisFlow; From c063a73742c14494e17231b1d6d933a3cc17dbf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 13:48:42 +0900 Subject: [PATCH 08/18] Fix autosize weirdness by specifying a constant size for the x axis --- .../Statistics/HitEventTimingDistributionGraph.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 48c18deaec..d83422a7b6 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -121,6 +121,8 @@ namespace osu.Game.Screens.Ranking.Statistics Container axisFlow; + const float axis_font_size = 12; + InternalChild = new GridContainer { Anchor = Anchor.Centre, @@ -142,7 +144,7 @@ namespace osu.Game.Screens.Ranking.Statistics axisFlow = new Container { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + Height = axis_font_size, } }, }, @@ -162,7 +164,7 @@ namespace osu.Game.Screens.Ranking.Statistics Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "0", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); for (int i = 1; i <= axis_points; i++) @@ -179,7 +181,7 @@ namespace osu.Game.Screens.Ranking.Statistics X = -position / 2, Alpha = alpha, Text = axisValue.ToString("-0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); axisFlow.Add(new OsuSpriteText @@ -190,7 +192,7 @@ namespace osu.Game.Screens.Ranking.Statistics X = position / 2, Alpha = alpha, Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); } } From d3e04fe594fe6e3a4c827aef0e9f16efd5ad9420 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:09:27 +0900 Subject: [PATCH 09/18] Colour centre bin in distribution graph differently --- .../Statistics/HitEventTimingDistributionGraph.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d83422a7b6..72d53e5d1b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 0; i < bars.Length; i++) { - bars[i] = new Bar + bars[i] = new Bar(i == timing_distribution_centre_bin_index) { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; @@ -199,17 +199,22 @@ namespace osu.Game.Screens.Ranking.Statistics private class Bar : CompositeDrawable { - public Bar() + public Bar(bool isCentre) { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; RelativeSizeAxes = Axes.Both; + var colour = Color4Extensions.FromHex("#66FFCC"); + + if (isCentre) + colour = colour.Lighten(1); + InternalChild = new Circle { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#66FFCC") + Colour = colour }; } } From 540d7d0e2c1b58be41d03c6026df9077f17af440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:34:33 +0900 Subject: [PATCH 10/18] Add the ability to set and show an offset value on timing distribution graph --- ...estSceneHitEventTimingDistributionGraph.cs | 5 +- .../HitEventTimingDistributionGraph.cs | 205 +++++++++++------- 2 files changed, 126 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index f31aec8975..48ce6145c0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -17,10 +17,13 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { + private HitEventTimingDistributionGraph graph; + [Test] public void TestManyDistributedEvents() { createTest(CreateDistributedHitEvents()); + AddStep("add adjustment", () => graph.UpdateOffset(10)); } [Test] @@ -68,7 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(events) + graph = new HitEventTimingDistributionGraph(events) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 72d53e5d1b..d510d995e2 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Statistics { @@ -58,6 +59,8 @@ namespace osu.Game.Screens.Ranking.Statistics private double binSize; private double hitOffset; + private Bar[] barDrawables; + [BackgroundDependencyLoader] private void load() { @@ -108,115 +111,151 @@ namespace osu.Game.Screens.Ranking.Statistics bins[index]++; } - int maxCount = bins.Max(); - var bars = new Drawable[total_timing_distribution_bins]; - - for (int i = 0; i < bars.Length; i++) + if (barDrawables != null) { - bars[i] = new Bar(i == timing_distribution_centre_bin_index) + for (int i = 0; i < barDrawables.Length; i++) { - Height = Math.Max(0.05f, (float)bins[i] / maxCount) - }; - } - - Container axisFlow; - - const float axis_font_size = 12; - - InternalChild = new GridContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.8f, - Content = new[] - { - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { bars } - } - }, - new Drawable[] - { - axisFlow = new Container - { - RelativeSizeAxes = Axes.X, - Height = axis_font_size, - } - }, - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), + barDrawables[i].UpdateOffset(bins[i]); } - }; - - // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - double maxValue = timing_distribution_bins * binSize; - double axisValueStep = maxValue / axis_points; - - axisFlow.Add(new OsuSpriteText + } + else { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "0", - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); + int maxCount = bins.Max(); + barDrawables = new Bar[total_timing_distribution_bins]; - for (int i = 1; i <= axis_points; i++) - { - double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); - float alpha = 1f - position * 0.8f; + for (int i = 0; i < barDrawables.Length; i++) + barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index); + + Container axisFlow; + + const float axis_font_size = 12; + + InternalChild = new GridContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { barDrawables } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + Height = axis_font_size, + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + }; + + // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + double maxValue = timing_distribution_bins * binSize; + double axisValueStep = maxValue / axis_points; axisFlow.Add(new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -position / 2, - Alpha = alpha, - Text = axisValue.ToString("-0"), + Text = "0", Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); - axisFlow.Add(new OsuSpriteText + for (int i = 1; i <= axis_points; i++) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = position / 2, - Alpha = alpha, - Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); + double axisValue = i * axisValueStep; + float position = (float)(axisValue / maxValue); + float alpha = 1f - position * 0.8f; + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = position / 2, + Alpha = alpha, + Text = axisValue.ToString("+0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + } } } private class Bar : CompositeDrawable { - public Bar(bool isCentre) + private readonly float value; + private readonly float maxValue; + + private readonly Circle boxOriginal; + private readonly Circle boxAdjustment; + + public Bar(float value, float maxValue, bool isCentre) { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; + this.value = value; + this.maxValue = maxValue; RelativeSizeAxes = Axes.Both; + Masking = true; - var colour = Color4Extensions.FromHex("#66FFCC"); - - if (isCentre) - colour = colour.Lighten(1); - - InternalChild = new Circle + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colour + boxOriginal = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), + Height = 0, + }, + boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }, }; } + + private const double duration = 300; + + protected override void LoadComplete() + { + base.LoadComplete(); + boxOriginal.ResizeHeightTo(Math.Clamp(value / maxValue, 0.05f, 1), duration, Easing.OutQuint); + } + + public void UpdateOffset(float adjustment) + { + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + } } } } From 92cd8ee29faf693bc6e02c87abc16b4e14ef40ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:56:46 +0900 Subject: [PATCH 11/18] Decrease overhead of hit event distribution tests --- .../TestSceneHitEventTimingDistributionGraph.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 48ce6145c0..7471b6acf2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -19,6 +20,8 @@ namespace osu.Game.Tests.Visual.Ranking { private HitEventTimingDistributionGraph graph; + private static readonly HitObject placeholder_object = new HitCircle(); + [Test] public void TestManyDistributedEvents() { @@ -35,13 +38,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAroundCentre() { - createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] public void TestZeroTimeOffset() { - createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] @@ -56,9 +59,9 @@ namespace osu.Game.Tests.Visual.Ranking createTest(Enumerable.Range(0, 100).Select(i => { if (i % 2 == 0) - return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null); + return new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null); - return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null); + return new HitEvent(30, HitResult.Miss, placeholder_object, placeholder_object, null); }).ToList()); } @@ -86,10 +89,10 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < range * 2; i++) { - int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)); + int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)) / 10; for (int j = 0; j < count; j++) - hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); + hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, placeholder_object, placeholder_object, null)); } return hitEvents; From 2785218b7932e93e61a654388e9dea6ccdaccac9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:59:53 +0900 Subject: [PATCH 12/18] Only apply animation if the bar is going to be larger than the minimum height --- .../Statistics/HitEventTimingDistributionGraph.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d510d995e2..d475556c84 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -213,6 +213,8 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly Circle boxOriginal; private readonly Circle boxAdjustment; + private const float minimum_height = 0.05f; + public Bar(float value, float maxValue, bool isCentre) { this.value = value; @@ -229,7 +231,7 @@ namespace osu.Game.Screens.Ranking.Statistics Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), - Height = 0, + Height = minimum_height, }, boxAdjustment = new Circle { @@ -249,7 +251,11 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void LoadComplete() { base.LoadComplete(); - boxOriginal.ResizeHeightTo(Math.Clamp(value / maxValue, 0.05f, 1), duration, Easing.OutQuint); + + float height = Math.Clamp(value / maxValue, minimum_height, 1); + + if (height > minimum_height) + boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) From 8c7b1e0aa8dcaf5a48134d53ea4a3d23a2054111 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 15:01:54 +0900 Subject: [PATCH 13/18] Only construct the adjustment portion of bars when required --- .../HitEventTimingDistributionGraph.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d475556c84..c823ed1f4c 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly float maxValue; private readonly Circle boxOriginal; - private readonly Circle boxAdjustment; + private Circle boxAdjustment; private const float minimum_height = 0.05f; @@ -233,16 +233,6 @@ namespace osu.Game.Screens.Ranking.Statistics Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), Height = minimum_height, }, - boxAdjustment = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = Color4.Yellow, - Blending = BlendingParameters.Additive, - Alpha = 0.6f, - Height = 0, - }, }; } @@ -260,6 +250,20 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { + if (boxAdjustment == null) + { + AddInternal(boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }); + } + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); } } From 5a164e4520aef238009bc95d398bb04c3ccaf8b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 15:19:55 +0900 Subject: [PATCH 14/18] Hide adjustment when no adjustment is applied --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index c823ed1f4c..bb42dda597 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -250,8 +250,13 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { + bool hasAdjustment = adjustment != value; + if (boxAdjustment == null) { + if (!hasAdjustment) + return; + AddInternal(boxAdjustment = new Circle { RelativeSizeAxes = Axes.Both, @@ -265,6 +270,7 @@ namespace osu.Game.Screens.Ranking.Statistics } boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } } From 08b3bc222d79a8d2a846ee11c1e300832d91a9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Mar 2022 16:42:51 +0100 Subject: [PATCH 15/18] Revert "Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available" This reverts commit 8115a4bb8fd9e2d53c40b8607c7ad99f0f62e9a0. Commit was cherrypicked out to a separate pull on a different merge base, then reverted in that pull, so it should be reverted here too. --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index f1cb0731fe..ff19dd874c 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,8 +14,6 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { - LookupKeyBindings = _ => "unknown"; - LookupSkinName = _ => "unknown"; } } } From f8ef352306a133d935bd9c441a6419de62a5d825 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:00:47 +0900 Subject: [PATCH 16/18] Don't consider judgements beneath the minimum height as being applicable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index bb42dda597..61b0cff8dd 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { - bool hasAdjustment = adjustment != value; + bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height; if (boxAdjustment == null) { From 06512e8bd90d96ee62daa1866841e73499608505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:01:22 +0900 Subject: [PATCH 17/18] Use `const` for minimum height specification in final usage location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 61b0cff8dd..f7c9d36cc4 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -269,7 +269,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From 0e8ad4b143785b571206729846856a1512c2e4e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:50:25 +0900 Subject: [PATCH 18/18] Switch step to `Until` steps due to `AddOnce` firing logic --- .../Visual/Gameplay/TestSceneBeatmapOffsetControl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 4b079cbb2c..8ca49837da 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -62,11 +62,11 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error); - AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } @@ -90,11 +90,11 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); - AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); }