diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 00a0d4a849..94c6130f15 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select collection", () => { - InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); + InputManager.MoveMouseTo(getCollectionDropdownItemAt(1)); InputManager.Click(MouseButton.Left); }); @@ -206,7 +206,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("click manage collections filter", () => { - InputManager.MoveMouseTo(getCollectionDropdownItems().Last()); + int lastItemIndex = control.ChildrenOfType().Single().Items.Count() - 1; + InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex)); InputManager.Click(MouseButton.Left); }); @@ -232,10 +233,10 @@ namespace osu.Game.Tests.Visual.SongSelect private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 - () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName))); + () => shouldContain == control.ChildrenOfType().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName)); private IconButton getAddOrRemoveButton(int index) - => getCollectionDropdownItems().ElementAt(index).ChildrenOfType().Single(); + => getCollectionDropdownItemAt(index).ChildrenOfType().Single(); private void addExpandHeaderStep() => AddStep("expand header", () => { @@ -249,7 +250,11 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index) + { + // todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079 + CollectionFilterMenuItem item = control.ChildrenOfType().Single().ItemSource.ElementAt(index); + return control.ChildrenOfType().Single(i => i.Item.Text.Value == item.CollectionName); + } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index fed5f68449..80be4412b3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -38,6 +39,9 @@ namespace osu.Game.Tests.Visual.UserInterface private TestModSelectOverlay modSelectOverlay = null!; + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + [BackgroundDependencyLoader] private void load() { @@ -566,17 +570,33 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestSearchFocusChangeViaKey() + public void TestTextSearchActiveByDefault() { + configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true); createScreen(); - const Key focus_switch_key = Key.Tab; + AddUntilStep("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); - AddStep("press tab", () => InputManager.Key(focus_switch_key)); - AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); - AddStep("press tab", () => InputManager.Key(focus_switch_key)); - AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + } + + [Test] + public void TestTextSearchNotActiveByDefault() + { + configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, false); + createScreen(); + + AddUntilStep("search text box not focused", () => !modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box focused", () => modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search text box unfocused", () => !modSelectOverlay.SearchTextBox.HasFocus); } [Test] diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index db7b27d30c..e43d8f4b02 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -43,12 +43,13 @@ namespace osu.Game.Collections private IDisposable? realmSubscription; + private readonly CollectionFilterMenuItem allBeatmapsItem = new AllBeatmapsCollectionFilterMenuItem(); + public CollectionDropdown() { ItemSource = filters; - Current.Value = new AllBeatmapsCollectionFilterMenuItem(); - + Current.Value = allBeatmapsItem; AlwaysShowSearchBar = true; } @@ -63,37 +64,51 @@ namespace osu.Game.Collections private void collectionsChanged(IRealmCollection collections, ChangeSet? changes) { - var selectedItem = SelectedItem?.Value?.Collection; - - var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem(); - - filters.Clear(); - filters.Add(allBeatmaps); - filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); - - if (ShowManageCollectionsItem) - filters.Add(new ManageCollectionsFilterMenuItem()); - - // This current update and schedule is required to work around dropdown headers not updating text even when the selected item - // changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue - // a warning that it's going to be a frustrating journey. - Current.Value = allBeatmaps; - Schedule(() => + if (changes == null) { - // current may have changed before the scheduled call is run. - if (Current.Value != allBeatmaps) - return; - - Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; - }); - - // Trigger a re-filter if the current item was in the change set. - if (selectedItem != null && changes != null) + filters.Add(allBeatmapsItem); + filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); + if (ShowManageCollectionsItem) + filters.Add(new ManageCollectionsFilterMenuItem()); + } + else { - foreach (int index in changes.ModifiedIndices) + foreach (int i in changes.DeletedIndices) + filters.RemoveAt(i + 1); + + foreach (int i in changes.InsertedIndices) + filters.Insert(i + 1, new CollectionFilterMenuItem(collections[i].ToLive(realm))); + + var selectedItem = SelectedItem?.Value; + + foreach (int i in changes.NewModifiedIndices) { - if (collections[index].ID == selectedItem.ID) + var updatedItem = collections[i]; + + // This is responsible for updating the state of the +/- button and the collection's name. + // TODO: we can probably make the menu items update with changes to avoid this. + filters.RemoveAt(i + 1); + filters.Insert(i + 1, new CollectionFilterMenuItem(updatedItem.ToLive(realm))); + + if (updatedItem.ID == selectedItem?.Collection?.ID) + { + // This current update and schedule is required to work around dropdown headers not updating text even when the selected item + // changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue + // a warning that it's going to be a frustrating journey. + Current.Value = allBeatmapsItem; + Schedule(() => + { + // current may have changed before the scheduled call is run. + if (Current.Value != allBeatmapsItem) + return; + + Current.Value = filters.SingleOrDefault(f => f.Collection?.ID == selectedItem.Collection?.ID) ?? filters[0]; + }); + + // Trigger an external re-filter if the current item was in the change set. RequestFilter?.Invoke(); + break; + } } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6ef55ab919..ea526c6d54 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -49,6 +49,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential); + SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true); SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); @@ -416,5 +417,6 @@ namespace osu.Game.Configuration AutomaticallyDownloadMissingBeatmaps, EditorShowSpeedChanges, TouchDisableGameplayTaps, + ModSelectTextSearchStartsActive, } } diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 612668171c..68c5c3ccbc 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -104,6 +104,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); + /// + /// "Automatically focus search text box in mod select" + /// + public static LocalisableString ModSelectTextSearchStartsActive => new TranslatableString(getKey(@"mod_select_text_search_starts_active"), @"Automatically focus search text box in mod select"); + /// /// "no limit" /// diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f2b3264a84..ce798ae752 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -115,6 +115,7 @@ namespace osu.Game.Overlays.Mods public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); private readonly BindableBool customisationVisible = new BindableBool(); + private Bindable textSearchStartsActive = null!; private ModSettingsArea modSettingsArea = null!; private ColumnScrollContainer columnScroll = null!; @@ -154,7 +155,7 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours, AudioManager audio) + private void load(OsuGameBase game, OsuColour colours, AudioManager audio, OsuConfigManager configManager) { Header.Title = ModSelectOverlayStrings.ModSelectTitle; Header.Description = ModSelectOverlayStrings.ModSelectDescription; @@ -282,6 +283,8 @@ namespace osu.Game.Overlays.Mods } globalAvailableMods.BindTo(game.AvailableMods); + + textSearchStartsActive = configManager.GetBindable(OsuSetting.ModSelectTextSearchStartsActive); } public override void Hide() @@ -617,6 +620,9 @@ namespace osu.Game.Overlays.Mods nonFilteredColumnCount += 1; } + + if (textSearchStartsActive.Value) + SearchTextBox.TakeFocus(); } protected override void PopOut() diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index addf5ce163..49bd17dfde 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -42,6 +42,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface ClassicDefault = ModSelectHotkeyStyle.Classic }, new SettingsCheckbox + { + LabelText = UserInterfaceStrings.ModSelectTextSearchStartsActive, + Current = config.GetBindable(OsuSetting.ModSelectTextSearchStartsActive), + ClassicDefault = false + }, + new SettingsCheckbox { LabelText = GameplaySettingsStrings.BackgroundBlur, Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur),