From 643c0605d85cf7808394c4b2fe3ea4f39b62906b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:14:38 +0900 Subject: [PATCH 01/19] Implement the freemod selection overlay --- .../TestSceneFreeModSelectOverlay.cs | 21 +++ osu.Game/Overlays/Mods/ModSection.cs | 15 ++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 +-- .../OnlinePlay/FreeModSelectOverlay.cs | 120 ++++++++++++++++++ 4 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs create mode 100644 osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs new file mode 100644 index 0000000000..26a0301d8a --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -0,0 +1,21 @@ +// 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.Framework.Graphics.Containers; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + { + [SetUp] + public new void Setup() => Schedule(() => + { + Child = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }; + }); + } +} diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index b3ddd30772..87a45ebf63 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -94,7 +94,20 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + /// + /// Selects all mods. + /// + public void SelectAll() + { + foreach (var button in buttons.Where(b => !b.Selected)) + button.SelectAt(0); + } + + /// + /// Deselects all mods. + /// + /// Set to true to bypass animations and update selections immediately. + public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); /// /// Deselect one or more mods in this section. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e064a6fb84..8225c1b6bb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,8 +37,11 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; + protected readonly Drawable MultiplierSection; protected readonly OsuSpriteText MultiplierLabel; + protected readonly FillFlowContainer FooterContainer; + protected override bool BlockNonPositionalInput => false; protected override bool DimMainContent => false; @@ -79,8 +82,6 @@ namespace osu.Game.Overlays.Mods private const float content_width = 0.8f; private const float footer_button_spacing = 20; - private readonly FillFlowContainer footerContainer; - private SampleChannel sampleOn, sampleOff; protected ModSelectOverlay() @@ -269,7 +270,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - footerContainer = new FillFlowContainer + FooterContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -283,7 +284,7 @@ namespace osu.Game.Overlays.Mods Vertical = 15, Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, - Children = new Drawable[] + Children = new[] { DeselectAllButton = new TriangleButton { @@ -310,7 +311,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - new FillFlowContainer + MultiplierSection = new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(footer_button_spacing / 2, 0), @@ -378,8 +379,8 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - footerContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); foreach (var section in ModSectionsContainer.Children) { @@ -393,8 +394,8 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); foreach (var section in ModSectionsContainer.Children) { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs new file mode 100644 index 0000000000..628199309a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -0,0 +1,120 @@ +// 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.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.OnlinePlay +{ + /// + /// A used for free-mod selection in online play. + /// + public class FreeModSelectOverlay : ModSelectOverlay + { + protected override bool Stacked => false; + + public FreeModSelectOverlay() + { + CustomiseButton.Alpha = 0; + MultiplierSection.Alpha = 0; + DeselectAllButton.Alpha = 0; + + Drawable selectAllButton; + Drawable deselectAllButton; + + FooterContainer.AddRange(new[] + { + selectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Select All", + Action = selectAll, + }, + // Unlike the base mod select overlay, this button deselects mods instantaneously. + deselectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Deselect All", + Action = deselectAll, + }, + }); + + FooterContainer.SetLayoutPosition(selectAllButton, -2); + FooterContainer.SetLayoutPosition(deselectAllButton, -1); + } + + private void selectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.SelectAll(); + } + + private void deselectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.DeselectAll(true); + } + + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); + + private class FreeModSection : ModSection + { + private HeaderCheckbox checkbox; + + public FreeModSection(ModType type) + : base(type) + { + } + + protected override Drawable CreateHeader(string text) => new Container + { + AutoSizeAxes = Axes.Y, + Width = 175, + Child = checkbox = new HeaderCheckbox + { + LabelText = text, + Changed = onCheckboxChanged + } + }; + + private void onCheckboxChanged(bool value) + { + foreach (var button in ButtonsContainer.OfType()) + { + if (value) + button.SelectAt(0); + else + button.Deselect(); + } + } + + protected override void Update() + { + base.Update(); + + var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + checkbox.Current.Value = validButtons.All(b => b.Selected); + } + } + + private class HeaderCheckbox : OsuCheckbox + { + public Action Changed; + + protected override void OnUserChange(bool value) + { + base.OnUserChange(value); + Changed?.Invoke(value); + } + } + } +} From f25535548ae1910215fff01186cac34285cdac81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:20:16 +0900 Subject: [PATCH 02/19] Fix buzzing on select all/deselect all --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8225c1b6bb..c308dc2451 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -495,11 +496,17 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(Color4.White, 200); } + private ScheduledDelegate sampleOnDelegate; + private ScheduledDelegate sampleOffDelegate; + private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) { - if (State.Value == Visibility.Visible) sampleOn?.Play(); + // Fixes buzzing when multiple mods are selected in the same frame. + sampleOnDelegate?.Cancel(); + if (State.Value == Visibility.Visible) + sampleOnDelegate = Scheduler.Add(() => sampleOn?.Play()); OnModSelected(selectedMod); @@ -507,7 +514,10 @@ namespace osu.Game.Overlays.Mods } else { - if (State.Value == Visibility.Visible) sampleOff?.Play(); + // Fixes buzzing when multiple mods are deselected in the same frame. + sampleOffDelegate?.Cancel(); + if (State.Value == Visibility.Visible) + sampleOffDelegate = Scheduler.Add(() => sampleOff?.Play()); } refreshSelectedMods(); From 5a56e2ba4b66ae8058157a3b32935f154d0f0c02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:29:00 +0900 Subject: [PATCH 03/19] Fix sound duplication due to checkbox --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 17 +++++++++++++---- .../Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 6593531099..517f83daa9 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -18,6 +18,11 @@ namespace osu.Game.Graphics.UserInterface public Color4 UncheckedColor { get; set; } = Color4.White; public int FadeDuration { get; set; } + /// + /// Whether to play sounds when the state changes as a result of user interaction. + /// + protected virtual bool PlaySoundsOnUserChange => true; + public string LabelText { set @@ -96,10 +101,14 @@ namespace osu.Game.Graphics.UserInterface protected override void OnUserChange(bool value) { base.OnUserChange(value); - if (value) - sampleChecked?.Play(); - else - sampleUnchecked?.Play(); + + if (PlaySoundsOnUserChange) + { + if (value) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + } } } } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 628199309a..608e58b534 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -110,6 +110,8 @@ namespace osu.Game.Screens.OnlinePlay { public Action Changed; + protected override bool PlaySoundsOnUserChange => false; + protected override void OnUserChange(bool value) { base.OnUserChange(value); From 6ff8e8dd37c3a79ea7b6c122f596272708cd6e58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:29:08 +0900 Subject: [PATCH 04/19] Disable a few mods by default --- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 608e58b534..5b9a19897f 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -18,8 +18,16 @@ namespace osu.Game.Screens.OnlinePlay { protected override bool Stacked => false; + public new Func IsValidMod + { + get => base.IsValidMod; + set => base.IsValidMod = m => m.HasImplementation && !m.RequiresConfiguration && !(m is ModAutoplay) && value(m); + } + public FreeModSelectOverlay() { + IsValidMod = m => true; + CustomiseButton.Alpha = 0; MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; From b2f1e133f86d3cf26d9581cdb45c0beecbe2ab5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:53:55 +0900 Subject: [PATCH 05/19] Allow checkbox nub to be moved to the left --- .../Graphics/UserInterface/OsuCheckbox.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 517f83daa9..313962d9c6 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleChecked; private SampleChannel sampleUnchecked; - public OsuCheckbox() + public OsuCheckbox(bool nubOnRight = true) { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -61,17 +61,24 @@ namespace osu.Game.Graphics.UserInterface { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding } - }, - Nub = new Nub - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = nub_padding }, }, + Nub = new Nub(), new HoverClickSounds() }; + if (nubOnRight) + { + Nub.Anchor = Anchor.CentreRight; + Nub.Origin = Anchor.CentreRight; + Nub.Margin = new MarginPadding { Right = nub_padding }; + labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding }; + } + else + { + Nub.Margin = new MarginPadding { Left = nub_padding }; + labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding }; + } + Nub.Current.BindTo(Current); Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; From 3148bbda2a09c00f38aec261bcb4b78559c9412e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:54:17 +0900 Subject: [PATCH 06/19] Allow custom font to be used in OsuCheckbox --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 313962d9c6..61a42dac23 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -57,7 +58,7 @@ namespace osu.Game.Graphics.UserInterface Children = new Drawable[] { - labelText = new OsuTextFlowContainer + labelText = new OsuTextFlowContainer(ApplyLabelParameters) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -84,6 +85,13 @@ namespace osu.Game.Graphics.UserInterface Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; } + /// + /// A function which can be overridden to change the parameters of the label's text. + /// + protected virtual void ApplyLabelParameters(SpriteText text) + { + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 48a58e790e2d2ec640d3273ee78e222ff6aed07e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:57:39 +0900 Subject: [PATCH 07/19] Don't specify arbitrary width --- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 5b9a19897f..c60afeb2aa 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay protected override Drawable CreateHeader(string text) => new Container { AutoSizeAxes = Axes.Y, - Width = 175, + RelativeSizeAxes = Axes.X, Child = checkbox = new HeaderCheckbox { LabelText = text, From b32e10514d1f8f48d7e580947176a56faa497d5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:58:02 +0900 Subject: [PATCH 08/19] Fix padding on label text not being double-applied (meaning no padding between nub and text) --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 61a42dac23..b80941c0bc 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -72,12 +72,12 @@ namespace osu.Game.Graphics.UserInterface Nub.Anchor = Anchor.CentreRight; Nub.Origin = Anchor.CentreRight; Nub.Margin = new MarginPadding { Right = nub_padding }; - labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding }; + labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 }; } else { Nub.Margin = new MarginPadding { Left = nub_padding }; - labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding }; + labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 }; } Nub.Current.BindTo(Current); From daf7ab942274a71298ff4caf3505cee52381aed0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:58:15 +0900 Subject: [PATCH 09/19] Apply the expected font to the checkbox's label --- .../Screens/OnlinePlay/FreeModSelectOverlay.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index c60afeb2aa..180f6079ac 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -3,8 +3,11 @@ using System; using System.Linq; +using System.Reflection.Emit; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -120,6 +123,19 @@ namespace osu.Game.Screens.OnlinePlay protected override bool PlaySoundsOnUserChange => false; + public HeaderCheckbox() + : base(false) + + { + } + + protected override void ApplyLabelParameters(SpriteText text) + { + base.ApplyLabelParameters(text); + + text.Font = OsuFont.GetFont(weight: FontWeight.Bold); + } + protected override void OnUserChange(bool value) { base.OnUserChange(value); From 4bfe3aabdc6c165e2bd368b9a70ab45ec4cc39b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 17:06:11 +0900 Subject: [PATCH 10/19] Simplify sound debounce logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c308dc2451..97902d1c15 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -496,17 +496,12 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(Color4.White, 200); } - private ScheduledDelegate sampleOnDelegate; - private ScheduledDelegate sampleOffDelegate; - private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) { - // Fixes buzzing when multiple mods are selected in the same frame. - sampleOnDelegate?.Cancel(); if (State.Value == Visibility.Visible) - sampleOnDelegate = Scheduler.Add(() => sampleOn?.Play()); + Scheduler.AddOnce(playSelectedSound); OnModSelected(selectedMod); @@ -514,15 +509,16 @@ namespace osu.Game.Overlays.Mods } else { - // Fixes buzzing when multiple mods are deselected in the same frame. - sampleOffDelegate?.Cancel(); if (State.Value == Visibility.Visible) - sampleOffDelegate = Scheduler.Add(() => sampleOff?.Play()); + Scheduler.AddOnce(playDeselectedSound); } refreshSelectedMods(); } + private void playSelectedSound() => sampleOn?.Play(); + private void playDeselectedSound() => sampleOff?.Play(); + /// /// Invoked when a new has been selected. /// From f23ca7c7cfd7bbd752e50464189c2c84546beba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 18:10:55 +0900 Subject: [PATCH 11/19] Centralise selection animation logic --- osu.Game/Overlays/Mods/ModSection.cs | 51 +++++++++++++------ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 - .../Overlays/Mods/SoloModSelectOverlay.cs | 2 +- .../OnlinePlay/FreeModSelectOverlay.cs | 25 +++++---- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 87a45ebf63..495b1c05cd 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -33,6 +33,8 @@ namespace osu.Game.Overlays.Mods private CancellationTokenSource modsLoadCts; + protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; + /// /// True when all mod icons have completed loading. /// @@ -49,7 +51,11 @@ namespace osu.Game.Overlays.Mods return new ModButton(m) { - SelectionChanged = Action, + SelectionChanged = mod => + { + ModButtonStateChanged(mod); + Action?.Invoke(mod); + }, }; }).ToArray(); @@ -78,6 +84,10 @@ namespace osu.Game.Overlays.Mods } } + protected virtual void ModButtonStateChanged(Mod mod) + { + } + private ModButton[] buttons = Array.Empty(); protected override bool OnKeyDown(KeyDownEvent e) @@ -94,44 +104,53 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } + private const double initial_multiple_selection_delay = 100; + + private readonly Queue pendingSelectionOperations = new Queue(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Scheduler.AddDelayed(() => + { + if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + dequeuedAction(); + }, initial_multiple_selection_delay, true); + } + /// /// Selects all mods. /// public void SelectAll() { + pendingSelectionOperations.Clear(); + foreach (var button in buttons.Where(b => !b.Selected)) - button.SelectAt(0); + pendingSelectionOperations.Enqueue(() => button.SelectAt(0)); } /// /// Deselects all mods. /// - /// Set to true to bypass animations and update selections immediately. - public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); + public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); /// /// Deselect one or more mods in this section. /// /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - public void DeselectTypes(IEnumerable modTypes, bool immediate = false) + public void DeselectTypes(IEnumerable modTypes) { - int delay = 0; + pendingSelectionOperations.Clear(); foreach (var button in buttons) { - Mod selected = button.SelectedMod; - if (selected == null) continue; + if (button.SelectedMod == null) continue; foreach (var type in modTypes) { - if (type.IsInstanceOfType(selected)) - { - if (immediate) - button.Deselect(); - else - Scheduler.AddDelayed(button.Deselect, delay += 50); - } + if (type.IsInstanceOfType(button.SelectedMod)) + pendingSelectionOperations.Enqueue(button.Deselect); } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 97902d1c15..4f65a39ed3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index d039ad1f98..aa0e78c126 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Mods base.OnModSelected(mod); foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods, true); + section.DeselectTypes(mod.IncompatibleMods); } } } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 180f6079ac..7bc226bb3f 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using System.Reflection.Emit; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -72,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay private void deselectAll() { foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(true); + section.DeselectAll(); } protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); @@ -99,21 +98,21 @@ namespace osu.Game.Screens.OnlinePlay private void onCheckboxChanged(bool value) { - foreach (var button in ButtonsContainer.OfType()) - { - if (value) - button.SelectAt(0); - else - button.Deselect(); - } + if (value) + SelectAll(); + else + DeselectAll(); } - protected override void Update() + protected override void ModButtonStateChanged(Mod mod) { - base.Update(); + base.ModButtonStateChanged(mod); - var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); - checkbox.Current.Value = validButtons.All(b => b.Selected); + if (!SelectionAnimationRunning) + { + var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + checkbox.Current.Value = validButtons.All(b => b.Selected); + } } } From 223b858227ed2a0d2aa3af284acbe581dbab84be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 18:56:40 +0900 Subject: [PATCH 12/19] Ramp the animation speed --- osu.Game/Overlays/Mods/ModSection.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 495b1c05cd..b9640a6026 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -104,19 +104,31 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - private const double initial_multiple_selection_delay = 100; + private const double initial_multiple_selection_delay = 120; + + private double selectionDelay = initial_multiple_selection_delay; + private double lastSelection; private readonly Queue pendingSelectionOperations = new Queue(); - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - Scheduler.AddDelayed(() => + if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay) { if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + { dequeuedAction(); - }, initial_multiple_selection_delay, true); + + selectionDelay = Math.Max(30, selectionDelay * 0.8f); + lastSelection = Time.Current; + } + else + { + selectionDelay = initial_multiple_selection_delay; + } + } } /// From a2674f3c3ff5a99d81127aae980c0ed05614fc47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 18:58:56 +0900 Subject: [PATCH 13/19] Add comments --- osu.Game/Overlays/Mods/ModSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index b9640a6026..353f779b4f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -121,11 +121,14 @@ namespace osu.Game.Overlays.Mods { dequeuedAction(); + // each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements). selectionDelay = Math.Max(30, selectionDelay * 0.8f); lastSelection = Time.Current; } else { + // reset the selection delay after all animations have been completed. + // this will cause the next action to be immediately performed. selectionDelay = initial_multiple_selection_delay; } } From bf239f8bef321dbb90e61ebe8e453a13a22f11c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:12:37 +0900 Subject: [PATCH 14/19] Flush animation on closing mod overlay --- osu.Game/Overlays/Mods/ModSection.cs | 9 +++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 353f779b4f..440abfb1c0 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -231,5 +231,14 @@ namespace osu.Game.Overlays.Mods Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = text }; + + /// + /// Play out all remaining animations immediately to leave mods in a good (final) state. + /// + public void FlushAnimation() + { + while (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + dequeuedAction(); + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 4f65a39ed3..93fe693937 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -379,6 +379,11 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); + foreach (var section in ModSectionsContainer) + { + section.FlushAnimation(); + } + FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); From 15062cc63f71ae8ae64b9a5343da7585a5bb5fa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:29:48 +0900 Subject: [PATCH 15/19] Fix intermittent test failures --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 92104cfc72..7f4dfaa9b0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime)); - AddAssert("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); + AddUntilStep("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); AddStep("make double time valid again", () => modSelect.IsValidMod = m => true); - AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); + AddUntilStep("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); } From 8f2f1a444f25058760583545c3390ce0562b3e5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:55:09 +0900 Subject: [PATCH 16/19] Avoid resetting selection on deselecting incompatibile types --- osu.Game/Overlays/Mods/ModSection.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 440abfb1c0..3f93eec7df 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -148,7 +148,11 @@ namespace osu.Game.Overlays.Mods /// /// Deselects all mods. /// - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + public void DeselectAll() + { + pendingSelectionOperations.Clear(); + DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + } /// /// Deselect one or more mods in this section. @@ -156,8 +160,6 @@ namespace osu.Game.Overlays.Mods /// The types of s which should be deselected. public void DeselectTypes(IEnumerable modTypes) { - pendingSelectionOperations.Clear(); - foreach (var button in buttons) { if (button.SelectedMod == null) continue; From cef16a9f61e6a9550f80b81f5c3cf6a2603c45e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:55:15 +0900 Subject: [PATCH 17/19] Add test coverage of animation / selection flushing --- .../TestSceneModSelectOverlay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 7f4dfaa9b0..44605f4994 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -46,6 +47,32 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); } + [Test] + public void TestAnimationFlushOnClose() + { + changeRuleset(0); + + AddStep("Select all fun mods", () => + { + modSelect.ModSectionsContainer + .Single(c => c.ModType == ModType.DifficultyIncrease) + .SelectAll(); + }); + + AddUntilStep("many mods selected", () => modDisplay.Current.Value.Count >= 5); + + AddStep("trigger deselect and close overlay", () => + { + modSelect.ModSectionsContainer + .Single(c => c.ModType == ModType.DifficultyIncrease) + .DeselectAll(); + + modSelect.Hide(); + }); + + AddAssert("all mods deselected", () => modDisplay.Current.Value.Count == 0); + } + [Test] public void TestOsuMods() { @@ -312,6 +339,9 @@ namespace osu.Game.Tests.Visual.UserInterface public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); + public new FillFlowContainer ModSectionsContainer => + base.ModSectionsContainer; + public ModButton GetModButton(Mod mod) { var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); From 794f9e5e932294d8c77e9cdc5b0b995b8f8d9062 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 22:53:41 +0900 Subject: [PATCH 18/19] Add missing centre anchor/origin --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index b80941c0bc..f6effa0834 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -76,6 +76,8 @@ namespace osu.Game.Graphics.UserInterface } else { + Nub.Anchor = Anchor.CentreLeft; + Nub.Origin = Anchor.CentreLeft; Nub.Margin = new MarginPadding { Left = nub_padding }; labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 }; } From 0750c3cb6a3ae9f8bc614fe61f082e936ba4f705 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 23:44:46 +0900 Subject: [PATCH 19/19] Add back immediate deselection flow to ensure user selections can occur without contention --- osu.Game/Overlays/Mods/ModSection.cs | 10 ++++++++-- osu.Game/Overlays/Mods/SoloModSelectOverlay.cs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 3f93eec7df..ecbcba7ad3 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -158,7 +158,8 @@ namespace osu.Game.Overlays.Mods /// Deselect one or more mods in this section. /// /// The types of s which should be deselected. - public void DeselectTypes(IEnumerable modTypes) + /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. + public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { foreach (var button in buttons) { @@ -167,7 +168,12 @@ namespace osu.Game.Overlays.Mods foreach (var type in modTypes) { if (type.IsInstanceOfType(button.SelectedMod)) - pendingSelectionOperations.Enqueue(button.Deselect); + { + if (immediate) + button.Deselect(); + else + pendingSelectionOperations.Enqueue(button.Deselect); + } } } } diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index aa0e78c126..d039ad1f98 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Mods base.OnModSelected(mod); foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods); + section.DeselectTypes(mod.IncompatibleMods, true); } } }