diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index f40c31b07f..c37bff2066 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; @@ -55,6 +56,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0); } + [Test] + public void TestSelectDeselectAllViaKeyboard() + { + createFreeModSelect(); + + AddStep("press ctrl+a", () => InputManager.Keys(PlatformAction.SelectAll)); + AddUntilStep("all mods selected", assertAllAvailableModsSelected); + + AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); + AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); + } + [Test] public void TestSelectDeselectAll() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 0b037a10cd..75e30f76c3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -416,6 +416,19 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value); } + [Test] + public void TestDeselectAllViaKey() + { + createScreen(); + changeRuleset(0); + + AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); + AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + + AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); + AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); + } + [Test] public void TestDeselectAllViaButton() { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 13a3f006fb..69ea6b00ca 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -109,6 +109,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F2, GlobalAction.SelectNextRandom), new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions), + new KeyBinding(InputKey.BackSpace, GlobalAction.DeselectAllMods), }; public IEnumerable AudioControlKeyBindings => new[] @@ -318,5 +319,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.SelectNextGroup))] SelectNextGroup, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))] + DeselectAllMods, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index b2f25de7f2..e392ae619f 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -207,7 +207,12 @@ namespace osu.Game.Localisation /// /// "Toggle Mod Select" /// - public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle Mod Select"); + public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select"); + + /// + /// "Deselect all mods" + /// + public static LocalisableString DeselectAllMods => new TranslatableString(getKey(@"deselect_all_mods"), @"Deselect all mods"); /// /// "Random" diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d068839ab0..1dad185b78 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -56,11 +56,32 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool ShowTotalMultiplier => true; + /// + /// Whether per-mod customisation controls are visible. + /// + protected virtual bool AllowCustomisation => true; + protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys); protected virtual IReadOnlyList ComputeNewModsFromSelection(IReadOnlyList oldSelection, IReadOnlyList newSelection) => newSelection; - protected virtual IEnumerable CreateFooterButtons() => createDefaultFooterButtons(); + protected virtual IEnumerable CreateFooterButtons() + { + if (AllowCustomisation) + { + yield return customisationButton = new ShearedToggleButton(BUTTON_WIDTH) + { + Text = ModSelectOverlayStrings.ModCustomisation, + Active = { BindTarget = customisationVisible } + }; + } + + yield return deselectAllButton = new ShearedButton(BUTTON_WIDTH) + { + Text = CommonStrings.DeselectAll, + Action = DeselectAll + }; + } private readonly Bindable>> availableMods = new Bindable>>(); private readonly Dictionary> localAvailableMods = new Dictionary>(); @@ -77,6 +98,7 @@ namespace osu.Game.Overlays.Mods private DifficultyMultiplierDisplay? multiplierDisplay; private ShearedToggleButton? customisationButton; + private ShearedButton? deselectAllButton; protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) @@ -201,7 +223,7 @@ namespace osu.Game.Overlays.Mods // This is an optimisation to prevent refreshing the available settings controls when it can be // reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectOverlay). - if (customisationButton != null) + if (AllowCustomisation) ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); SelectedMods.BindValueChanged(val => @@ -256,21 +278,6 @@ namespace osu.Game.Overlays.Mods }; } - private ShearedButton[] createDefaultFooterButtons() - => new[] - { - customisationButton = new ShearedToggleButton(BUTTON_WIDTH) - { - Text = ModSelectOverlayStrings.ModCustomisation, - Active = { BindTarget = customisationVisible } - }, - new ShearedButton(BUTTON_WIDTH) - { - Text = CommonStrings.DeselectAll, - Action = DeselectAll - } - }; - private void createLocalMods() { localAvailableMods.Clear(); @@ -508,6 +515,10 @@ namespace osu.Game.Overlays.Mods hideOverlay(true); return true; } + + case GlobalAction.DeselectAllMods: + deselectAllButton?.TriggerClick(); + return true; } return base.OnPressed(e); diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 6e1c9b7a59..f494f9799a 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -4,7 +4,11 @@ using System; using osu.Game.Overlays; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -13,16 +17,20 @@ using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay { - public class FreeModSelectOverlay : ModSelectOverlay + public class FreeModSelectOverlay : ModSelectOverlay, IKeyBindingHandler { protected override bool ShowTotalMultiplier => false; + protected override bool AllowCustomisation => false; + public new Func IsValidMod { get => base.IsValidMod; set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } + private ShearedButton selectAllButton; + public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) { @@ -31,22 +39,32 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); - protected override IEnumerable CreateFooterButtons() => new[] - { - new ShearedButton(BUTTON_WIDTH) + protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons().Prepend( + selectAllButton = new ShearedButton(BUTTON_WIDTH) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Text = CommonStrings.SelectAll, Action = SelectAll - }, - new ShearedButton(BUTTON_WIDTH) + }); + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) + return false; + + switch (e.Action) { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = CommonStrings.DeselectAll, - Action = DeselectAll + case PlatformAction.SelectAll: + selectAllButton.TriggerClick(); + return true; } - }; + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } }