diff --git a/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs index efb65bb0a8..85cde966b1 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -25,9 +25,15 @@ namespace osu.Game.Tests.Visual.Settings private Container content = null!; protected override Container Content => content; + private OsuConfigManager localConfig = null!; + private AudioOffsetAdjustControl adjustControl = null!; + [BackgroundDependencyLoader] private void load() { + localConfig = new OsuConfigManager(LocalStorage); + Dependencies.CacheAs(localConfig); + base.Content.AddRange(new Drawable[] { tracker, @@ -41,17 +47,21 @@ namespace osu.Game.Tests.Visual.Settings }); } - [Test] - public void TestBehaviour() + [SetUp] + public void SetUp() => Schedule(() => { - AddStep("create control", () => Child = new AudioOffsetAdjustControl + Child = adjustControl = new AudioOffsetAdjustControl { - Current = new BindableDouble - { - MinValue = -500, - MaxValue = 500 - } - }); + Current = localConfig.GetBindable(OsuSetting.AudioOffset), + }; + + localConfig.SetValue(OsuSetting.AudioOffset, 0.0); + tracker.ClearHistory(); + }); + + [Test] + public void TestDisplay() + { AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo { HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(RNG.NextDouble(-100, 100)), @@ -59,5 +69,63 @@ namespace osu.Game.Tests.Visual.Settings })); AddStep("clear history", () => tracker.ClearHistory()); } + + [Test] + public void TestBehaviour() + { + AddStep("set score with -20ms", () => setScore(-20)); + AddAssert("suggested global offset is 20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(20)); + AddStep("clear history", () => tracker.ClearHistory()); + + AddStep("set score with 40ms", () => setScore(40)); + AddAssert("suggested global offset is -40ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(-40)); + AddStep("clear history", () => tracker.ClearHistory()); + } + + [Test] + public void TestNonZeroGlobalOffset() + { + AddStep("set global offset to -20ms", () => localConfig.SetValue(OsuSetting.AudioOffset, -20.0)); + AddStep("set score with -20ms", () => setScore(-20)); + AddAssert("suggested global offset is 0ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(0)); + AddStep("clear history", () => tracker.ClearHistory()); + + AddStep("set global offset to 20ms", () => localConfig.SetValue(OsuSetting.AudioOffset, 20.0)); + AddStep("set score with 40ms", () => setScore(40)); + AddAssert("suggested global offset is -20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(-20)); + AddStep("clear history", () => tracker.ClearHistory()); + } + + [Test] + public void TestMultiplePlays() + { + AddStep("set score with -20ms", () => setScore(-20)); + AddStep("set score with -10ms", () => setScore(-10)); + AddAssert("suggested global offset is 15ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(15)); + AddStep("clear history", () => tracker.ClearHistory()); + + AddStep("set score with -20ms", () => setScore(-20)); + AddStep("set global offset to 30ms", () => localConfig.SetValue(OsuSetting.AudioOffset, 30.0)); + AddStep("set score with 10ms", () => setScore(10)); + AddAssert("suggested global offset is 20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(20)); + AddStep("clear history", () => tracker.ClearHistory()); + } + + private void setScore(double averageHitError) + { + statics.SetValue(Static.LastLocalUserScore, new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(averageHitError), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }); + } + + protected override void Dispose(bool isDisposing) + { + if (localConfig.IsNotNull()) + localConfig.Dispose(); + + base.Dispose(isDisposing); + } } } diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 08a816930e..85751e7457 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Header bool anyInfoAdded = false; anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); - anyInfoAdded |= tryAddInfo(OsuIcon.Heart, user.Interests); + anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Heart, user.Interests); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); if (anyInfoAdded) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 08bf4b0dad..90f5a59215 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { public partial class AudioOffsetAdjustControl : SettingsItem { + public IBindable SuggestedOffset => ((AudioOffsetPreview)Control).SuggestedOffset; + [BackgroundDependencyLoader] private void load() { @@ -44,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private readonly IBindableList averageHitErrorHistory = new BindableList(); - private readonly Bindable suggestedOffset = new Bindable(); + public readonly Bindable SuggestedOffset = new Bindable(); private Container notchContainer = null!; private TextFlowContainer hintText = null!; @@ -90,8 +92,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Text = "Apply suggested offset", Action = () => { - if (suggestedOffset.Value.HasValue) - current.Value = suggestedOffset.Value.Value; + if (SuggestedOffset.Value.HasValue) + current.Value = SuggestedOffset.Value.Value; hitErrorTracker.ClearHistory(); } } @@ -104,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio base.LoadComplete(); averageHitErrorHistory.BindCollectionChanged(updateDisplay, true); - suggestedOffset.BindValueChanged(_ => updateHintText(), true); + SuggestedOffset.BindValueChanged(_ => updateHintText(), true); } private void updateDisplay(object? _, NotifyCollectionChangedEventArgs e) @@ -143,17 +145,17 @@ namespace osu.Game.Overlays.Settings.Sections.Audio break; } - suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; + SuggestedOffset.Value = averageHitErrorHistory.Any() ? averageHitErrorHistory.Average(dataPoint => dataPoint.SuggestedGlobalAudioOffset) : null; } private float getXPositionForOffset(double offset) => (float)(Math.Clamp(offset, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); private void updateHintText() { - hintText.Text = suggestedOffset.Value == null + hintText.Text = SuggestedOffset.Value == null ? @"Play a few beatmaps to receive a suggested offset!" - : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {suggestedOffset.Value:N0} ms."; - applySuggestion.Enabled.Value = suggestedOffset.Value != null; + : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms."; + applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } } } diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index bc638b44ac..422599a4a8 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -31,6 +31,9 @@ namespace osu.Game.Screens.Menu /// public partial class MainMenuButton : BeatSyncedContainer, IStateful { + public const float BOUNCE_COMPRESSION = 0.9f; + public const float HOVER_SCALE = 1.2f; + public const float BOUNCE_ROTATION = 8; public event Action? StateChanged; public readonly Key[] TriggerKeys; @@ -125,8 +128,9 @@ namespace osu.Game.Screens.Menu Shadow = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(30), + Size = new Vector2(32), Position = new Vector2(0, 0), + Margin = new MarginPadding { Top = -4 }, Icon = symbol }, new OsuSpriteText @@ -136,6 +140,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Position = new Vector2(0, 35), + Margin = new MarginPadding { Left = -3 }, Text = text } } @@ -153,14 +158,14 @@ namespace osu.Game.Screens.Menu double duration = timingPoint.BeatLength / 2; - icon.RotateTo(rightward ? 10 : -10, duration * 2, Easing.InOutSine); + icon.RotateTo(rightward ? BOUNCE_ROTATION : -BOUNCE_ROTATION, duration * 2, Easing.InOutSine); icon.Animate( i => i.MoveToY(-10, duration, Easing.Out), - i => i.ScaleTo(1, duration, Easing.Out) + i => i.ScaleTo(HOVER_SCALE, duration, Easing.Out) ).Then( i => i.MoveToY(0, duration, Easing.In), - i => i.ScaleTo(new Vector2(1, 0.9f), duration, Easing.In) + i => i.ScaleTo(new Vector2(HOVER_SCALE, HOVER_SCALE * BOUNCE_COMPRESSION), duration, Easing.In) ); rightward = !rightward; @@ -177,8 +182,8 @@ namespace osu.Game.Screens.Menu double duration = TimeUntilNextBeat; icon.ClearTransforms(); - icon.RotateTo(rightward ? -10 : 10, duration, Easing.InOutSine); - icon.ScaleTo(new Vector2(1, 0.9f), duration, Easing.Out); + icon.RotateTo(rightward ? -BOUNCE_ROTATION : BOUNCE_ROTATION, duration, Easing.InOutSine); + icon.ScaleTo(new Vector2(HOVER_SCALE, HOVER_SCALE * BOUNCE_COMPRESSION), duration, Easing.Out); return true; }