From 556be552c3ff68e78d68c25ad9aaea67049f2690 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 1 Apr 2026 04:59:47 -0700 Subject: [PATCH] Fix song select dropdowns not being navigable with keyboard (#37158) - Closes https://github.com/ppy/osu/issues/31684 Uses the global action variant of up and down for dropdown menus. It is already used in multiplayer/playlist room/beatmap navigation and gameplay menu overlays. Comment wording is derived from: https://github.com/ppy/osu/blob/fc817627e56b7db1c46e2f00fc9a3bd14af51211/osu.Game/Graphics/UserInterface/FocusedTextBox.cs#L73-L74 --- .../Visual/SongSelect/TestSceneSongSelect.cs | 27 ++++++++++++++++ .../Graphics/UserInterface/OsuDropdown.cs | 31 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelect.cs index 6eddc8e1d8..24f3255b17 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelect.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Mods; @@ -23,6 +24,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.Leaderboards; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Resources; using osuTK.Input; using BeatmapCarousel = osu.Game.Screens.Select.BeatmapCarousel; @@ -430,6 +432,31 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("osu! cookie visible", () => this.ChildrenOfType().Single().Alpha, () => Is.Not.Zero); } + [Test] + public void TestDropdownKeyboardNavigation() + { + ImportBeatmapForRuleset(0); + + LoadSongSelect(); + + BeatmapInfo? firstBeatmap = null; + AddStep("store first difficulty", () => firstBeatmap = Beatmap.Value.BeatmapInfo); + + AddStep("click sort dropdown", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("press up arrow", () => InputManager.Key(Key.Up)); + AddStep("press up arrow", () => InputManager.Key(Key.Up)); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddAssert("sort mode is length", () => this.ChildrenOfType>().Single().Current.Value, () => Is.EqualTo(SortMode.Length)); + AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(firstBeatmap)); + } + #endregion #region Footer diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index bbc826a7a7..16f5ab2a8b 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -54,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface #region OsuDropdownMenu - public partial class OsuDropdownMenu : DropdownMenu + public partial class OsuDropdownMenu : DropdownMenu, IKeyBindingHandler { public override bool HandleNonPositionalInput => State == MenuState.Open; @@ -163,6 +163,35 @@ namespace osu.Game.Graphics.UserInterface protected override ScrollContainer CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction); + public bool OnPressed(KeyBindingPressEvent e) + { + // logic copied from https://github.com/ppy/osu-framework/blob/baf865f1fd9e677310e7e432a7c6af99db7db914/osu.Framework/Graphics/UserInterface/Dropdown.cs#L702-L717 + var visibleMenuItemsList = VisibleMenuItems.ToList(); + + if (visibleMenuItemsList.Count > 0) + { + var currentPreselected = PreselectedItem; + int targetPreselectionIndex = visibleMenuItemsList.IndexOf(currentPreselected); + + switch (e.Action) + { + case GlobalAction.SelectPrevious: + PreselectItem(targetPreselectionIndex - 1); + return true; + + case GlobalAction.SelectNext: + PreselectItem(targetPreselectionIndex + 1); + return true; + } + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + #region DrawableOsuDropdownMenuItem public partial class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem