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); + } + } + } +}