diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index cad98185ce..233aaf2ed9 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests get { if (content == null) - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + base.Content.Add(content = new OsuInputManager(new OsuRuleset().RulesetInfo)); return content; } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index fa6c9da174..9b36b064bc 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new TaikoInputManager(new RulesetInfo { ID = 1 }) + SetContents(() => new TaikoInputManager(new TaikoRuleset().RulesetInfo) { RelativeSizeAxes = Axes.Both, Child = new Container diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.cs new file mode 100644 index 0000000000..0ac65b357c --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneSongSelectFooter : OsuManualInputManagerTestScene + { + public TestSceneSongSelectFooter() + { + AddStep("Create footer", () => + { + Footer footer; + AddRange(new Drawable[] + { + footer = new Footer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + + footer.AddButton(new FooterButtonMods(), null); + footer.AddButton(new FooterButtonRandom + { + NextRandom = () => { }, + PreviousRandom = () => { }, + }, null); + footer.AddButton(new FooterButtonOptions(), null); + }); + } + } +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index d12eaa10f6..cd8b486f23 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -64,12 +64,20 @@ namespace osu.Game.Input.Bindings protected override void ReloadMappings() { + var defaults = DefaultKeyBindings.ToList(); + if (ruleset != null && !ruleset.ID.HasValue) // if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings. // fallback to defaults instead. - KeyBindings = DefaultKeyBindings; + KeyBindings = defaults; else - KeyBindings = store.Query(ruleset?.ID, variant).ToList(); + { + KeyBindings = store.Query(ruleset?.ID, variant) + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)).ToList(); + } } } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index cda962f0ab..042960d54c 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -27,7 +27,11 @@ namespace osu.Game.Input.Bindings handler = game; } - public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings); + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings + .Concat(EditorKeyBindings) + .Concat(InGameKeyBindings) + .Concat(SongSelectKeyBindings) + .Concat(AudioControlKeyBindings); public IEnumerable GlobalKeyBindings => new[] { @@ -80,6 +84,14 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; + public IEnumerable SongSelectKeyBindings => new[] + { + new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection), + new KeyBinding(InputKey.F2, GlobalAction.SelectNextRandom), + new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), + new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions) + }; + public IEnumerable AudioControlKeyBindings => new[] { new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), @@ -217,5 +229,18 @@ namespace osu.Game.Input.Bindings [Description("Toggle in-game interface")] ToggleInGameInterface, + + // Song select keybindings + [Description("Toggle Mod Select")] + ToggleModSelection, + + [Description("Random")] + SelectNextRandom, + + [Description("Rewind")] + SelectPreviousRandom, + + [Description("Beatmap Options")] + ToggleBeatmapOptions, } } diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 9a27c55c53..c905397e77 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.KeyBinding { Add(new DefaultBindingsSubsection(manager)); Add(new AudioControlKeyBindingsSubsection(manager)); + Add(new SongSelectKeyBindingSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager)); Add(new EditorKeyBindingsSubsection(manager)); } @@ -36,6 +37,17 @@ namespace osu.Game.Overlays.KeyBinding } } + private class SongSelectKeyBindingSubsection : KeyBindingsSubsection + { + protected override string Header => "Song Select"; + + public SongSelectKeyBindingSubsection(GlobalActionContainer manager) + : base(null) + { + Defaults = manager.SongSelectKeyBindings; + } + } + private class InGameKeyBindingsSubsection : KeyBindingsSubsection { protected override string Header => "In Game"; diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index cd7c1c449f..7528651fd9 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -4,7 +4,6 @@ using System; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,10 +12,12 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using osu.Framework.Input.Bindings; namespace osu.Game.Screens.Select { - public class FooterButton : OsuClickableContainer + public class FooterButton : OsuClickableContainer, IKeyBindingHandler { public const float SHEAR_WIDTH = 7.5f; @@ -105,6 +106,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Child = SpriteText = new OsuSpriteText { + AlwaysPresent = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, } @@ -118,7 +120,7 @@ namespace osu.Game.Screens.Select public Action Hovered; public Action HoverLost; - public Key? Hotkey; + public GlobalAction? Hotkey; protected override void UpdateAfterChildren() { @@ -168,15 +170,17 @@ namespace osu.Game.Screens.Select return base.OnClick(e); } - protected override bool OnKeyDown(KeyDownEvent e) + public virtual bool OnPressed(GlobalAction action) { - if (!e.Repeat && e.Key == Hotkey) + if (action == Hotkey) { Click(); return true; } - return base.OnKeyDown(e); + return false; } + + public virtual void OnReleased(GlobalAction action) { } } } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 02333da0dc..b98b48a0c0 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -using osuTK.Input; +using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select { @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select lowMultiplierColour = colours.Red; highMultiplierColour = colours.Green; Text = @"mods"; - Hotkey = Key.F1; + Hotkey = GlobalAction.ToggleModSelection; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs index c000d8a8c8..e549656785 100644 --- a/osu.Game/Screens/Select/FooterButtonOptions.cs +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics; -using osuTK.Input; +using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select { @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Blue; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"options"; - Hotkey = Key.F3; + Hotkey = GlobalAction.ToggleBeatmapOptions; } } } diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index a42e6721db..2d14111137 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -1,35 +1,23 @@ // 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 osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osuTK.Input; +using osu.Game.Input.Bindings; +using osuTK; namespace osu.Game.Screens.Select { public class FooterButtonRandom : FooterButton { - private readonly SpriteText secondaryText; - private bool secondaryActive; + public Action NextRandom { get; set; } + public Action PreviousRandom { get; set; } - public FooterButtonRandom() - { - TextContainer.Add(secondaryText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = @"rewind", - Alpha = 0, - }); - - // force both text sprites to always be present to avoid width flickering while they're being swapped out - SpriteText.AlwaysPresent = secondaryText.AlwaysPresent = true; - } + private bool rewindSearch; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -37,34 +25,57 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Green; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"random"; - Hotkey = Key.F2; - } - protected override bool OnKeyDown(KeyDownEvent e) - { - secondaryActive = e.ShiftPressed; - updateText(); - return base.OnKeyDown(e); - } - - protected override void OnKeyUp(KeyUpEvent e) - { - secondaryActive = e.ShiftPressed; - updateText(); - base.OnKeyUp(e); - } - - private void updateText() - { - if (secondaryActive) + Action = () => { - SpriteText.FadeOut(120, Easing.InQuad); - secondaryText.FadeIn(120, Easing.InQuad); + if (rewindSearch) + { + const double fade_time = 500; + + OsuSpriteText rewindSpriteText; + + TextContainer.Add(rewindSpriteText = new OsuSpriteText + { + Alpha = 0, + Text = @"rewind", + AlwaysPresent = true, // make sure the button is sized large enough to always show this + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + rewindSpriteText.FadeOutFromOne(fade_time, Easing.In); + rewindSpriteText.MoveTo(Vector2.Zero).MoveTo(new Vector2(0, 10), fade_time, Easing.In); + rewindSpriteText.Expire(); + + SpriteText.FadeInFromZero(fade_time, Easing.In); + + PreviousRandom.Invoke(); + } + else + { + NextRandom.Invoke(); + } + }; + } + + public override bool OnPressed(GlobalAction action) + { + rewindSearch = action == GlobalAction.SelectPreviousRandom; + + if (action != GlobalAction.SelectNextRandom && action != GlobalAction.SelectPreviousRandom) + { + return false; } - else + + Click(); + return true; + } + + public override void OnReleased(GlobalAction action) + { + if (action == GlobalAction.SelectPreviousRandom) { - SpriteText.FadeIn(120, Easing.InQuad); - secondaryText.FadeOut(120, Easing.InQuad); + rewindSearch = false; } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b7f7c40539..215700d87c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -307,7 +307,11 @@ namespace osu.Game.Screens.Select protected virtual IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[] { (new FooterButtonMods { Current = Mods }, ModSelect), - (new FooterButtonRandom { Action = triggerRandom }, null), + (new FooterButtonRandom + { + NextRandom = () => Carousel.SelectNextRandom(), + PreviousRandom = Carousel.SelectPreviousRandom + }, null), (new FooterButtonOptions(), BeatmapOptions) }; @@ -522,14 +526,6 @@ namespace osu.Game.Screens.Select } } - private void triggerRandom() - { - if (GetContainingInputManager().CurrentState.Keyboard.ShiftPressed) - Carousel.SelectPreviousRandom(); - else - Carousel.SelectNextRandom(); - } - public override void OnEntering(IScreen last) { base.OnEntering(last);