From 3c6141f233d0eb829c831fea7d838f7f2da2ecbd Mon Sep 17 00:00:00 2001 From: Cootz Date: Thu, 27 Apr 2023 17:08:29 +0300 Subject: [PATCH 01/79] Add `ModSearch` --- osu.Game/Overlays/Mods/ModSearch.cs | 8 ++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSearch.cs diff --git a/osu.Game/Overlays/Mods/ModSearch.cs b/osu.Game/Overlays/Mods/ModSearch.cs new file mode 100644 index 0000000000..c96be4d817 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSearch.cs @@ -0,0 +1,8 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.Mods; + +public partial class ModSearch +{ +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 16602db4be..edf16c09fb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -188,8 +188,8 @@ namespace osu.Game.Overlays.Mods { MainAreaContent.Add(new Container { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, AutoSizeAxes = Axes.X, Height = ModsEffectDisplay.HEIGHT, Margin = new MarginPadding { Horizontal = 100 }, @@ -197,7 +197,7 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.Centre, Origin = Anchor.Centre - }, + } }); } From b795e8ac5a25eac8619bee8fbfe510d2edd91573 Mon Sep 17 00:00:00 2001 From: Cootz Date: Thu, 27 Apr 2023 17:26:35 +0300 Subject: [PATCH 02/79] Use `ModSearch` in `ModeSelectOverlay` --- osu.Game/Overlays/Mods/ModSearch.cs | 4 +++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSearch.cs b/osu.Game/Overlays/Mods/ModSearch.cs index c96be4d817..0a82d967af 100644 --- a/osu.Game/Overlays/Mods/ModSearch.cs +++ b/osu.Game/Overlays/Mods/ModSearch.cs @@ -1,8 +1,10 @@ // 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.Containers; + namespace osu.Game.Overlays.Mods; -public partial class ModSearch +public partial class ModSearch : Container { } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index edf16c09fb..31f2c87100 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -110,6 +110,8 @@ namespace osu.Game.Overlays.Mods private DifficultyMultiplierDisplay? multiplierDisplay; + private ModSearch? modSearch; + protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } @@ -201,6 +203,16 @@ namespace osu.Game.Overlays.Mods }); } + MainAreaContent.Add(new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + Height = ModsEffectDisplay.HEIGHT, + Margin = new MarginPadding { Horizontal = 100 }, + Child = modSearch = new ModSearch() + }); + FooterContent.Child = footerButtonFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, From a6ca0497399d5e83faca452782c49fa74c2c406a Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 2 May 2023 14:15:33 +0300 Subject: [PATCH 03/79] Manually implement @bdach prototype --- .../UserInterface/TestSceneModColumn.cs | 2 +- .../Mods/Input/ClassicModHotkeyHandler.cs | 2 +- .../Mods/Input/SequentialModHotkeyHandler.cs | 2 +- osu.Game/Overlays/Mods/ModColumn.cs | 12 +++--- osu.Game/Overlays/Mods/ModPanel.cs | 30 +++++++++++--- osu.Game/Overlays/Mods/ModPresetPanel.cs | 11 ++++++ osu.Game/Overlays/Mods/ModSelectColumn.cs | 9 ++++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 39 ++++++++++++------- osu.Game/Overlays/Mods/ModSelectPanel.cs | 26 ++++++++++++- osu.Game/Overlays/Mods/ModState.cs | 11 +++++- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 2 +- 11 files changed, 112 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index a11000214c..3f5676ee24 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -291,7 +291,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) - modState.Filtered.Value = filter?.Invoke(modState.Mod) == false; + modState.MatchingFilter.Value = filter?.Invoke(modState.Mod) == false; } private partial class TestModColumn : ModColumn diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 4f3c18fc43..566e741880 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Mods.Input if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) return false; - var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && !modState.Filtered.Value).ToArray(); + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.MatchingFilter.Value).ToArray(); if (matchingMods.Length == 0) return false; diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index dedb556304..b4ec8ad4c8 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Mods.Input if (index < 0) return false; - var modState = availableMods.Where(modState => !modState.Filtered.Value).ElementAtOrDefault(index); + var modState = availableMods.Where(modState => modState.MatchingFilter.Value).ElementAtOrDefault(index); if (modState == null) return false; diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 5d9f616e5f..df2a7780d3 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Mods foreach (var mod in availableMods) { mod.Active.BindValueChanged(_ => updateState()); - mod.Filtered.BindValueChanged(_ => updateState()); + mod.MatchingFilter.BindValueChanged(_ => updateState()); } updateState(); @@ -145,12 +145,12 @@ namespace osu.Game.Overlays.Mods private void updateState() { - Alpha = availableMods.All(mod => mod.Filtered.Value) ? 0 : 1; + Alpha = availableMods.All(mod => !mod.MatchingFilter.Value) ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { - toggleAllCheckbox.Alpha = availableMods.Any(panel => !panel.Filtered.Value) ? 1 : 0; - toggleAllCheckbox.Current.Value = availableMods.Where(panel => !panel.Filtered.Value).All(panel => panel.Active.Value); + toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.MatchingFilter.Value) ? 1 : 0; + toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.MatchingFilter.Value).All(panel => panel.Active.Value); } } @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in availableMods.Where(b => !b.Active.Value && !b.Filtered.Value)) + foreach (var button in availableMods.Where(b => !b.Active.Value && b.MatchingFilter.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = true); } @@ -206,7 +206,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in availableMods.Where(b => b.Active.Value && !b.Filtered.Value)) + foreach (var button in availableMods.Where(b => b.Active.Value && b.MatchingFilter.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = false); } diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index b5fee9d116..a5ff52dfd2 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -11,11 +14,10 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public partial class ModPanel : ModSelectPanel + public partial class ModPanel : ModSelectPanel, IConditionalFilterable { public Mod Mod => modState.Mod; public override BindableBool Active => modState.Active; - public BindableBool Filtered => modState.Filtered; protected override float IdleSwitchWidth => 54; protected override float ExpandedSwitchWidth => 70; @@ -54,7 +56,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Filtered.BindValueChanged(_ => updateFilterState(), true); + canBeShown.BindTo(modState.ValidForSelection); } protected override void Select() @@ -71,11 +73,29 @@ namespace osu.Game.Overlays.Mods #region Filtering support - private void updateFilterState() + public override IEnumerable FilterTerms => new[] { - this.FadeTo(Filtered.Value ? 0 : 1); + Mod.Name, + Mod.Acronym, + Mod.Description + }; + + public override bool MatchingFilter + { + get => modState.MatchingFilter.Value; + set + { + if (modState.MatchingFilter.Value == value) + return; + + modState.MatchingFilter.Value = value; + this.FadeTo(value ? 1 : 0); + } } + private readonly BindableBool canBeShown = new BindableBool(true); + IBindable IConditionalFilterable.CanBeShown => canBeShown; + #endregion } } diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 6e12e34124..5b6146e106 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; @@ -80,6 +81,16 @@ namespace osu.Game.Overlays.Mods Active.Value = new HashSet(Preset.Value.Mods).SetEquals(selectedMods.Value); } + #region Filtering support + + public override IEnumerable FilterTerms => new LocalisableString[] + { + Preset.Value.Name, + Preset.Value.Description + }; + + #endregion + #region IHasCustomTooltip public ModPreset TooltipContent => Preset.Value; diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index e6d7bcd97d..8bf3fd404f 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -43,10 +43,15 @@ namespace osu.Game.Overlays.Mods /// public readonly Bindable Active = new BindableBool(true); + public string SearchTerm + { + set => ItemsFlow.SearchTerm = value; + } + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; protected readonly Container ControlContainer; - protected readonly FillFlowContainer ItemsFlow; + protected readonly SearchContainer ItemsFlow; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -150,7 +155,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Both, ClampExtension = 100, ScrollbarOverlapsContent = false, - Child = ItemsFlow = new FillFlowContainer + Child = ItemsFlow = new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 31f2c87100..12e894cfba 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -108,6 +108,8 @@ namespace osu.Game.Overlays.Mods private ColumnFlowContainer columnFlow = null!; private FillFlowContainer footerButtonFlow = null!; + private Container aboveColumnsContent = null!; + private ShearedSearchTextBox searchTextBox = null!; private DifficultyMultiplierDisplay? multiplierDisplay; private ModSearch? modSearch; @@ -148,6 +150,17 @@ namespace osu.Game.Overlays.Mods MainAreaContent.AddRange(new Drawable[] { + aboveColumnsContent = new Container + { + RelativeSizeAxes = Axes.X, + Height = ModsEffectDisplay.HEIGHT, + Padding = new MarginPadding { Horizontal = 100 }, + Child = searchTextBox = new ShearedSearchTextBox + { + HoldFocus = false, + Width = 300 + } + }, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -188,18 +201,10 @@ namespace osu.Game.Overlays.Mods if (ShowTotalMultiplier) { - MainAreaContent.Add(new Container + aboveColumnsContent.Add(multiplierDisplay = new DifficultyMultiplierDisplay { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - AutoSizeAxes = Axes.X, - Height = ModsEffectDisplay.HEIGHT, - Margin = new MarginPadding { Horizontal = 100 }, - Child = multiplierDisplay = new DifficultyMultiplierDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre }); } @@ -271,6 +276,12 @@ namespace osu.Game.Overlays.Mods customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); + searchTextBox.Current.BindValueChanged(query => + { + foreach (var column in columnFlow.Columns) + column.SearchTerm = query.NewValue; + }, true); + // Start scrolled slightly to the right to give the user a sense that // there is more horizontal content available. ScheduleAfterChildren(() => @@ -352,7 +363,7 @@ namespace osu.Game.Overlays.Mods private void filterMods() { foreach (var modState in allAvailableMods) - modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod); + modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } private void updateMultiplier() @@ -487,7 +498,7 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => modState.Filtered.Value); + bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => !modState.MatchingFilter.Value); double delay = allFiltered ? 0 : nonFilteredColumnCount * 30; double duration = allFiltered ? 0 : fade_in_duration; @@ -549,7 +560,7 @@ namespace osu.Game.Overlays.Mods if (column is ModColumn modColumn) { - allFiltered = modColumn.AvailableMods.All(modState => modState.Filtered.Value); + allFiltered = modColumn.AvailableMods.All(modState => !modState.MatchingFilter.Value); modColumn.FlushPendingSelections(); } diff --git a/osu.Game/Overlays/Mods/ModSelectPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs index 81285833bd..90663d083c 100644 --- a/osu.Game/Overlays/Mods/ModSelectPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -24,7 +25,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public abstract partial class ModSelectPanel : OsuClickableContainer, IHasAccentColour + public abstract partial class ModSelectPanel : OsuClickableContainer, IHasAccentColour, IFilterable { public abstract BindableBool Active { get; } @@ -263,5 +264,28 @@ namespace osu.Game.Overlays.Mods TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } + + #region IFilterable + + public abstract IEnumerable FilterTerms { get; } + + private bool matchingFilter; + + public virtual bool MatchingFilter + { + get => matchingFilter; + set + { + if (matchingFilter == value) + return; + + matchingFilter = value; + this.FadeTo(value ? 1 : 0); + } + } + + public bool FilteringActive { set { } } + + #endregion } } diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs index 3ee890e876..35b264fe71 100644 --- a/osu.Game/Overlays/Mods/ModState.cs +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -32,9 +32,16 @@ namespace osu.Game.Overlays.Mods public bool PendingConfiguration { get; set; } /// - /// Whether the mod is currently filtered out due to not matching imposed criteria. + /// Whether the mod is currently valid for selection. + /// This can be in scenarios such as the free mod select overlay, where not all mods are selectable + /// regardless of search criteria imposed by the user selecting. /// - public BindableBool Filtered { get; } = new BindableBool(); + public BindableBool ValidForSelection { get; } = new BindableBool(true); + + /// + /// Whether the mod is matching the current filter, i.e. it is available for user selection. + /// + public BindableBool MatchingFilter { get; } = new BindableBool(true); public ModState(Mod mod) { diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index f4b8025227..212563de7d 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods { Enabled.Value = availableMods.Value .SelectMany(pair => pair.Value) - .Any(modState => !modState.Active.Value && !modState.Filtered.Value); + .Any(modState => !modState.Active.Value && !modState.MatchingFilter.Value); } public bool OnPressed(KeyBindingPressEvent e) From f0d35eb12be0262e892b4fc614128860007107c7 Mon Sep 17 00:00:00 2001 From: Cootz Date: Wed, 3 May 2023 13:12:11 +0300 Subject: [PATCH 04/79] Update testing --- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Visual/UserInterface/TestSceneModColumn.cs | 12 ++++++------ .../UserInterface/TestSceneModSelectOverlay.cs | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index c0b6a0beab..dd1400b36e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual.Multiplayer () => this.ChildrenOfType() .Single() .ChildrenOfType() - .Where(panel => !panel.Filtered.Value) + .Where(panel => panel.MatchingFilter) .All(b => b.Mod.GetType() != type)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 8816787ceb..8fd16fb723 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("mod select contains only double time mod", () => this.ChildrenOfType().Single().UserModsSelectOverlay .ChildrenOfType() - .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); + .SingleOrDefault(panel => panel.MatchingFilter)?.Mod is OsuModDoubleTime); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 3f5676ee24..ec6084aa8e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -106,26 +106,26 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); - AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.MatchingFilter) == 2); clickToggle(); AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning); - AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => !panel.Filtered.Value)); + AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => panel.MatchingFilter)); AddStep("unset filter", () => setFilter(null)); - AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.MatchingFilter)); AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value); AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); - AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => !panel.Filtered.Value) == 2); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.MatchingFilter) == 2); AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); AddStep("filter out everything", () => setFilter(_ => false)); - AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => panel.Filtered.Value)); + AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.MatchingFilter)); AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent); AddStep("inset filter", () => setFilter(null)); - AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => !panel.Filtered.Value)); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.MatchingFilter)); AddUntilStep("checkbox visible", () => column.ChildrenOfType().Single().IsPresent); void clickToggle() => AddStep("click toggle", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 5ccaebd721..9c6ec653e9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -431,15 +431,15 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); - AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); + AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.MatchingFilter)); AddStep("make double time invalid", () => modSelectOverlay.IsValidMod = m => !(m is OsuModDoubleTime)); - AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value)); - AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); + AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => !panel.MatchingFilter)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => panel.MatchingFilter)); AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true); - AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value)); - AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value)); + AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.MatchingFilter)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => panel.MatchingFilter)); } [Test] @@ -465,7 +465,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set ruleset", () => Ruleset.Value = testRuleset.RulesetInfo); waitForColumnLoad(); - AddAssert("unimplemented mod panel is filtered", () => getPanelForMod(typeof(TestUnimplementedMod)).Filtered.Value); + AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).MatchingFilter); } [Test] From 152d2678d52c74d562c1c959da5502c960664ed1 Mon Sep 17 00:00:00 2001 From: Cootz Date: Wed, 3 May 2023 14:00:46 +0300 Subject: [PATCH 05/79] Fix `ModSelectColumn` completely disappear from `ModSelectOverlay` --- osu.Game/Overlays/Mods/ModSearch.cs | 10 -------- osu.Game/Overlays/Mods/ModSearchContainer.cs | 25 ++++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectColumn.cs | 6 ++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 4 files changed, 30 insertions(+), 15 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/ModSearch.cs create mode 100644 osu.Game/Overlays/Mods/ModSearchContainer.cs diff --git a/osu.Game/Overlays/Mods/ModSearch.cs b/osu.Game/Overlays/Mods/ModSearch.cs deleted file mode 100644 index 0a82d967af..0000000000 --- a/osu.Game/Overlays/Mods/ModSearch.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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.Containers; - -namespace osu.Game.Overlays.Mods; - -public partial class ModSearch : Container -{ -} diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs new file mode 100644 index 0000000000..29cc7d8132 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs @@ -0,0 +1,25 @@ +// 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.Containers; + +namespace osu.Game.Overlays.Mods; + +public partial class ModSearchContainer : SearchContainer +{ + /// + /// A string that should match the children + /// + public string ForcedSearchTerm + { + get => SearchTerm; + set + { + if (value == SearchTerm) + return; + + SearchTerm = value; + Update(); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 8bf3fd404f..c80eb8c09c 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -45,13 +45,13 @@ namespace osu.Game.Overlays.Mods public string SearchTerm { - set => ItemsFlow.SearchTerm = value; + set => ItemsFlow.ForcedSearchTerm = value; } protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; protected readonly Container ControlContainer; - protected readonly SearchContainer ItemsFlow; + protected readonly ModSearchContainer ItemsFlow; private readonly TextFlowContainer headerText; private readonly Box headerBackground; @@ -155,7 +155,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Both, ClampExtension = 100, ScrollbarOverlapsContent = false, - Child = ItemsFlow = new SearchContainer + Child = ItemsFlow = new ModSearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 12e894cfba..9efcd24048 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Mods private ShearedSearchTextBox searchTextBox = null!; private DifficultyMultiplierDisplay? multiplierDisplay; - private ModSearch? modSearch; + private ModSearchContainer? modSearch; protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } @@ -215,7 +215,7 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.X, Height = ModsEffectDisplay.HEIGHT, Margin = new MarginPadding { Horizontal = 100 }, - Child = modSearch = new ModSearch() + Child = modSearch = new ModSearchContainer() }); FooterContent.Child = footerButtonFlow = new FillFlowContainer From 1ac9d900e1f0ea8b66e5722174fc3f7f960b9e6b Mon Sep 17 00:00:00 2001 From: Cootz Date: Thu, 4 May 2023 19:24:37 +0300 Subject: [PATCH 06/79] Clear search on `ModSelectOverlay.Hide` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9efcd24048..fa39c2b09d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -112,8 +112,6 @@ namespace osu.Game.Overlays.Mods private ShearedSearchTextBox searchTextBox = null!; private DifficultyMultiplierDisplay? multiplierDisplay; - private ModSearchContainer? modSearch; - protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } @@ -215,7 +213,7 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.X, Height = ModsEffectDisplay.HEIGHT, Margin = new MarginPadding { Horizontal = 100 }, - Child = modSearch = new ModSearchContainer() + Child = new ModSearchContainer() }); FooterContent.Child = footerButtonFlow = new FillFlowContainer @@ -243,6 +241,14 @@ namespace osu.Game.Overlays.Mods globalAvailableMods.BindTo(game.AvailableMods); } + public override void Hide() + { + base.Hide(); + + //We want to clear search for next user iteraction with mod overlay + searchTextBox.Current.Value = string.Empty; + } + private ModSettingChangeTracker? modSettingChangeTracker; protected override void LoadComplete() From 54757df51fefc2c8c3a19b7dc340036ceb5b6a29 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 5 May 2023 09:31:27 +0300 Subject: [PATCH 07/79] Fix mod deselect button not working properly when search applied --- osu.Game/Overlays/Mods/ModColumn.cs | 6 +++++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 10 ---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index df2a7780d3..5da57ee4ed 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -206,8 +206,12 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in availableMods.Where(b => b.Active.Value && b.MatchingFilter.Value)) + foreach (var button in availableMods.Where(b => b.Active.Value)) pendingSelectionOperations.Enqueue(() => button.Active.Value = false); + + //If column is hidden trigger selection manually + if (Alpha == 0f) + Update(); } /// diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fa39c2b09d..d2af305d68 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -206,16 +206,6 @@ namespace osu.Game.Overlays.Mods }); } - MainAreaContent.Add(new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - Height = ModsEffectDisplay.HEIGHT, - Margin = new MarginPadding { Horizontal = 100 }, - Child = new ModSearchContainer() - }); - FooterContent.Child = footerButtonFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, From ab94b4dce194c85942bfa94d6c1f7bb16073dc93 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 5 May 2023 09:52:09 +0300 Subject: [PATCH 08/79] Update `ModPresetPanel.FilterTerms` to account mod names and acronyms --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 5b6146e106..5bc16abcab 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -83,11 +83,20 @@ namespace osu.Game.Overlays.Mods #region Filtering support - public override IEnumerable FilterTerms => new LocalisableString[] + public override IEnumerable FilterTerms => getFilterTerms(); + + private IEnumerable getFilterTerms() { - Preset.Value.Name, - Preset.Value.Description - }; + yield return Preset.Value.Name; + yield return Preset.Value.Description; + + foreach (Mod mod in Preset.Value.Mods) + { + yield return mod.Name; + yield return mod.Acronym; + yield return mod.Description; + } + } #endregion From 7422b5285ce8e9631111c20ab9a1baf40d81152a Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 5 May 2023 22:41:30 +0300 Subject: [PATCH 09/79] Fix wrong filtering in testing --- .../UserInterface/TestSceneModColumn.cs | 2 +- .../TestSceneModSelectOverlay.cs | 12 ++++---- osu.Game/Overlays/Mods/ModColumn.cs | 3 +- osu.Game/Overlays/Mods/ModPanel.cs | 30 +++++++++++++++++-- osu.Game/Overlays/Mods/ModState.cs | 5 ++++ osu.Game/Overlays/Mods/SelectAllModsButton.cs | 2 +- 6 files changed, 43 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index ec6084aa8e..2ae95a3a09 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -291,7 +291,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) - modState.MatchingFilter.Value = filter?.Invoke(modState.Mod) == false; + modState.MatchingFilter.Value = filter?.Invoke(modState.Mod) ?? true; } private partial class TestModColumn : ModColumn diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9c6ec653e9..006a6abbc2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -431,15 +431,15 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); - AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.MatchingFilter)); + AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.IsValid)); AddStep("make double time invalid", () => modSelectOverlay.IsValidMod = m => !(m is OsuModDoubleTime)); - AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => !panel.MatchingFilter)); - AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => panel.MatchingFilter)); + AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => !panel.IsValid)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => panel.IsValid)); AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true); - AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.MatchingFilter)); - AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => panel.MatchingFilter)); + AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.IsValid)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => panel.IsValid)); } [Test] @@ -465,7 +465,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set ruleset", () => Ruleset.Value = testRuleset.RulesetInfo); waitForColumnLoad(); - AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).MatchingFilter); + AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).IsValid); } [Test] diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 5da57ee4ed..c8822d78fd 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -47,6 +47,7 @@ namespace osu.Game.Overlays.Mods { mod.Active.BindValueChanged(_ => updateState()); mod.MatchingFilter.BindValueChanged(_ => updateState()); + mod.ValidForSelection.BindValueChanged(_ => updateState()); } updateState(); @@ -145,7 +146,7 @@ namespace osu.Game.Overlays.Mods private void updateState() { - Alpha = availableMods.All(mod => !mod.MatchingFilter.Value) ? 0 : 1; + Alpha = availableMods.All(mod => !mod.MatchingFilter.Value || !mod.ValidForSelection.Value) ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index a5ff52dfd2..cd94226d8f 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -56,7 +56,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - canBeShown.BindTo(modState.ValidForSelection); + modState.ValidForSelection.BindValueChanged(_ => updateFilterState()); + modState.MatchingFilter.BindValueChanged(_ => updateFilterState(), true); } protected override void Select() @@ -71,6 +72,23 @@ namespace osu.Game.Overlays.Mods Active.Value = false; } + /// + /// Determine if is valid and can be shown + /// + public bool IsValid => modState.IsValid; + + public bool ValidForSelection + { + get => modState.ValidForSelection.Value; + set + { + if (modState.ValidForSelection.Value == value) + return; + + modState.ValidForSelection.Value = value; + } + } + #region Filtering support public override IEnumerable FilterTerms => new[] @@ -89,13 +107,21 @@ namespace osu.Game.Overlays.Mods return; modState.MatchingFilter.Value = value; - this.FadeTo(value ? 1 : 0); } } + /// + /// This property is always because it affects search result + /// private readonly BindableBool canBeShown = new BindableBool(true); + IBindable IConditionalFilterable.CanBeShown => canBeShown; + private void updateFilterState() + { + this.FadeTo(IsValid ? 1 : 0); + } + #endregion } } diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs index 35b264fe71..be770ec937 100644 --- a/osu.Game/Overlays/Mods/ModState.cs +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -38,6 +38,11 @@ namespace osu.Game.Overlays.Mods /// public BindableBool ValidForSelection { get; } = new BindableBool(true); + /// + /// Determine if is valid and can be shown + /// + public bool IsValid => MatchingFilter.Value && ValidForSelection.Value; + /// /// Whether the mod is matching the current filter, i.e. it is available for user selection. /// diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index 212563de7d..dad4f7b629 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods { Enabled.Value = availableMods.Value .SelectMany(pair => pair.Value) - .Any(modState => !modState.Active.Value && !modState.MatchingFilter.Value); + .Any(modState => !modState.Active.Value && modState.IsValid); } public bool OnPressed(KeyBindingPressEvent e) From a226caff560afc897e3c596e08a0fcfa74b6a3a0 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sat, 6 May 2023 11:09:44 +0300 Subject: [PATCH 10/79] Fix testing --- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Visual/UserInterface/TestSceneModColumn.cs | 14 +++++++------- .../Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 2 +- .../Mods/Input/SequentialModHotkeyHandler.cs | 2 +- osu.Game/Overlays/Mods/ModColumn.cs | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 8fd16fb723..40acea475b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("mod select contains only double time mod", () => this.ChildrenOfType().Single().UserModsSelectOverlay .ChildrenOfType() - .SingleOrDefault(panel => panel.MatchingFilter)?.Mod is OsuModDoubleTime); + .SingleOrDefault(panel => panel.IsValid)?.Mod is OsuModDoubleTime); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 2ae95a3a09..fc1db3c644 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -106,26 +106,26 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); - AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.MatchingFilter) == 2); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.IsValid) == 2); clickToggle(); AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning); - AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => panel.MatchingFilter)); + AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => panel.IsValid)); AddStep("unset filter", () => setFilter(null)); - AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.MatchingFilter)); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.IsValid)); AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value); AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); - AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.MatchingFilter) == 2); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.IsValid) == 2); AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); AddStep("filter out everything", () => setFilter(_ => false)); - AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.MatchingFilter)); + AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.IsValid)); AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent); AddStep("inset filter", () => setFilter(null)); - AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.MatchingFilter)); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.IsValid)); AddUntilStep("checkbox visible", () => column.ChildrenOfType().Single().IsPresent); void clickToggle() => AddStep("click toggle", () => @@ -291,7 +291,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) - modState.MatchingFilter.Value = filter?.Invoke(modState.Mod) ?? true; + modState.ValidForSelection.Value = filter?.Invoke(modState.Mod) ?? true; } private partial class TestModColumn : ModColumn diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 566e741880..343f7242f1 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Mods.Input if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) return false; - var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.MatchingFilter.Value).ToArray(); + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.IsValid).ToArray(); if (matchingMods.Length == 0) return false; diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index b4ec8ad4c8..cbfa96307d 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Mods.Input if (index < 0) return false; - var modState = availableMods.Where(modState => modState.MatchingFilter.Value).ElementAtOrDefault(index); + var modState = availableMods.Where(modState => modState.IsValid).ElementAtOrDefault(index); if (modState == null) return false; diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index c8822d78fd..b53e621759 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -146,12 +146,12 @@ namespace osu.Game.Overlays.Mods private void updateState() { - Alpha = availableMods.All(mod => !mod.MatchingFilter.Value || !mod.ValidForSelection.Value) ? 0 : 1; + Alpha = availableMods.All(mod => !mod.IsValid) ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { - toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.MatchingFilter.Value) ? 1 : 0; - toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.MatchingFilter.Value).All(panel => panel.Active.Value); + toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.IsValid) ? 1 : 0; + toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.IsValid).All(panel => panel.Active.Value); } } @@ -196,7 +196,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in availableMods.Where(b => !b.Active.Value && b.MatchingFilter.Value)) + foreach (var button in availableMods.Where(b => !b.Active.Value && b.IsValid)) pendingSelectionOperations.Enqueue(() => button.Active.Value = true); } From cbb9f0100e2dad808ae7309d74b8f3bb701aa5d1 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sat, 6 May 2023 11:27:06 +0300 Subject: [PATCH 11/79] Update `PopIn` and `PopOut` filtering. Expose `SearchTerm` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d2af305d68..cb8eddca62 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -64,6 +64,21 @@ namespace osu.Game.Overlays.Mods } } + /// + /// Search term applied on mod overlay + /// + public string SearchTerm + { + get => searchTextBox.Current.Value; + set + { + if (searchTextBox.Current.Value == value) + return; + + searchTextBox.Current.Value = value; + } + } + /// /// Whether the total score multiplier calculated from the current selected set of mods should be shown. /// @@ -494,7 +509,7 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => !modState.MatchingFilter.Value); + bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => !modState.IsValid); double delay = allFiltered ? 0 : nonFilteredColumnCount * 30; double duration = allFiltered ? 0 : fade_in_duration; @@ -556,7 +571,7 @@ namespace osu.Game.Overlays.Mods if (column is ModColumn modColumn) { - allFiltered = modColumn.AvailableMods.All(modState => !modState.MatchingFilter.Value); + allFiltered = modColumn.AvailableMods.All(modState => !modState.IsValid); modColumn.FlushPendingSelections(); } From 5aca3a78dac91fb940d3ccdf82e077db374f708a Mon Sep 17 00:00:00 2001 From: Cootz Date: Sat, 6 May 2023 12:21:32 +0300 Subject: [PATCH 12/79] Add basic tests for external search --- .../UserInterface/TestSceneModColumn.cs | 43 ++++++++++++ .../TestSceneModSelectOverlay.cs | 65 +++++++++++++++++-- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index fc1db3c644..394a38fe5d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -288,6 +288,49 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2); } + [Test] + public void TestApplySearchTerms() + { + Mod hidden = getExampleModsFor(ModType.DifficultyIncrease).Where(modState => modState.Mod is ModHidden).Select(modState => modState.Mod).Single(); + + ModColumn column = null!; + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = column = new ModColumn(ModType.DifficultyIncrease, false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AvailableMods = getExampleModsFor(ModType.DifficultyIncrease) + } + }); + + applySearchAndAssert(hidden.Name); + + clearSearch(); + + applySearchAndAssert(hidden.Acronym); + + clearSearch(); + + applySearchAndAssert(hidden.Description.ToString()); + + void applySearchAndAssert(string searchTerm) + { + AddStep("search by mod name", () => column.SearchTerm = searchTerm); + + AddAssert("only hidden is visible", () => column.ChildrenOfType().Where(panel => panel.IsValid).All(panel => panel.Mod is ModHidden)); + } + + void clearSearch() + { + AddStep("clear search", () => column.SearchTerm = string.Empty); + + AddAssert("all mods are visible", () => column.ChildrenOfType().All(panel => panel.IsValid)); + } + } + private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 006a6abbc2..e8a835a330 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -521,8 +521,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); } + /// + /// Internal search applies from code by setting + /// [Test] - public void TestColumnHiding() + public void TestColumnHidingOnInternalSearch() { AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay { @@ -551,6 +554,56 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("3 columns visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 3); } + /// + /// External search applies by user by entering search term into search bar + /// + [Test] + public void TestColumnHidingOnExternalSearch() + { + AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + SelectedMods = { BindTarget = SelectedMods } + }); + waitForColumnLoad(); + changeRuleset(0); + + AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); + + AddStep("set search", () => modSelectOverlay.SearchTerm = "HD"); + AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 1); + + AddStep("filter out everything", () => modSelectOverlay.SearchTerm = "Some long search term with no matches"); + AddAssert("no columns visible", () => this.ChildrenOfType().All(col => !col.IsPresent)); + + AddStep("clear search bar", () => modSelectOverlay.SearchTerm = ""); + AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); + } + + [Test] + public void TestHidingOverlayClearsSearch() + { + AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + SelectedMods = { BindTarget = SelectedMods } + }); + waitForColumnLoad(); + changeRuleset(0); + + AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); + + AddStep("set search", () => modSelectOverlay.SearchTerm = "fail"); + AddAssert("one column visible", () => this.ChildrenOfType().Count(col => col.IsPresent) == 2); + + AddStep("hide", () => modSelectOverlay.Hide()); + AddStep("show", () => modSelectOverlay.Show()); + + AddAssert("all columns visible", () => this.ChildrenOfType().All(col => col.IsPresent)); + } + [Test] public void TestColumnHidingOnRulesetChange() { @@ -605,12 +658,10 @@ namespace osu.Game.Tests.Visual.UserInterface { public override string ShortName => "unimplemented"; - public override IEnumerable GetModsFor(ModType type) - { - if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }); - - return base.GetModsFor(type); - } + public override IEnumerable GetModsFor(ModType type) => + type == ModType.Conversion + ? base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() }) + : base.GetModsFor(type); } } } From 4d235105d14d0b1ed91fedae09c6eaea6bdf13bf Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 7 May 2023 15:14:49 +0300 Subject: [PATCH 13/79] Convert `ModSearchContainer` to block-scoped namespace --- osu.Game/Overlays/Mods/ModSearchContainer.cs | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs index 29cc7d8132..f9bf11d1f1 100644 --- a/osu.Game/Overlays/Mods/ModSearchContainer.cs +++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs @@ -3,23 +3,24 @@ using osu.Framework.Graphics.Containers; -namespace osu.Game.Overlays.Mods; - -public partial class ModSearchContainer : SearchContainer +namespace osu.Game.Overlays.Mods { - /// - /// A string that should match the children - /// - public string ForcedSearchTerm + public partial class ModSearchContainer : SearchContainer { - get => SearchTerm; - set + /// + /// A string that should match the children + /// + public string ForcedSearchTerm { - if (value == SearchTerm) - return; + get => SearchTerm; + set + { + if (value == SearchTerm) + return; - SearchTerm = value; - Update(); + SearchTerm = value; + Update(); + } } } } From 60bad35145ac63e0d357d801c4a6fcf2a56a5812 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 7 May 2023 15:34:01 +0300 Subject: [PATCH 14/79] Remove weird update usage when 'deselect all' pressed --- osu.Game/Overlays/Mods/ModColumn.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index b53e621759..71d964c618 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -208,11 +208,12 @@ namespace osu.Game.Overlays.Mods pendingSelectionOperations.Clear(); foreach (var button in availableMods.Where(b => b.Active.Value)) - pendingSelectionOperations.Enqueue(() => button.Active.Value = false); - - //If column is hidden trigger selection manually - if (Alpha == 0f) - Update(); + { + if (Alpha == 0f) + button.Active.Value = false; //If column is hidden change state manually without any animation + else + pendingSelectionOperations.Enqueue(() => button.Active.Value = false); + } } /// From 4c3af6ecfed1f17dcf3f60193247c3394a4f152c Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 7 May 2023 15:50:21 +0300 Subject: [PATCH 15/79] Add test coverage for deselect all with filtered mods selected --- .../TestSceneModSelectOverlay.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index e8a835a330..8e4f437f44 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -502,6 +502,31 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); } + [Test] + public void TestDeselectAllViaButton_WithSearchApplied() + { + createScreen(); + changeRuleset(0); + + AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + + AddStep("select DT + HD + RD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModRandom() }); + AddAssert("DT + HD + RD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 3); + AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + + AddStep("apply search", () => modSelectOverlay.SearchTerm = "Easy"); + AddAssert("DT + HD + RD are hidden and selected", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.IsValid && panel.Active.Value) == 3); + AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + + AddStep("click deselect all button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); + AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + } + [Test] public void TestCloseViaBackButton() { From 2467813d8199473f2b37041fb005065c312dd7d2 Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 9 May 2023 16:14:42 +0300 Subject: [PATCH 16/79] Block `deselect all` short key when using the search box --- .../TestSceneModSelectOverlay.cs | 20 +++++++++++++++++++ .../UserInterface/ShearedSearchTextBox.cs | 2 ++ .../Overlays/Mods/DeselectAllModsButton.cs | 8 +++++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 20 +++++++++++-------- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 8e4f437f44..d1cbe7d91c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -481,6 +481,26 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); } + [Test] + public void TestDeselectAllViaKey_WithSearchApplied() + { + createScreen(); + changeRuleset(0); + + AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); + AddStep("focus on search", () => modSelectOverlay.SearchTextBox.TakeFocus()); + AddStep("apply search", () => modSelectOverlay.SearchTerm = "Easy"); + AddAssert("DT + HD selected and hidden", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.IsValid && panel.Active.Value) == 2); + + AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); + AddAssert("DT + HD still selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddAssert("search term changed", () => modSelectOverlay.SearchTerm == "Eas"); + + AddStep("kill focus", () => modSelectOverlay.SearchTextBox.KillFocus()); + AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); + AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); + } + [Test] public void TestDeselectAllViaButton() { diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index 7bd083f9d5..a6954fafb1 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -37,6 +37,8 @@ namespace osu.Game.Graphics.UserInterface set => textBox.HoldFocus = value; } + public new bool HasFocus => textBox.HasFocus; + public void TakeFocus() => textBox.TakeFocus(); public void KillFocus() => textBox.KillFocus(); diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs index 3e5a3b12d1..d4d196508f 100644 --- a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs @@ -18,6 +18,7 @@ namespace osu.Game.Overlays.Mods public partial class DeselectAllModsButton : ShearedButton, IKeyBindingHandler { private readonly Bindable> selectedMods = new Bindable>(); + private readonly ShearedSearchTextBox searchTextBox; public DeselectAllModsButton(ModSelectOverlay modSelectOverlay) : base(ModSelectOverlay.BUTTON_WIDTH) @@ -25,6 +26,8 @@ namespace osu.Game.Overlays.Mods Text = CommonStrings.DeselectAll; Action = modSelectOverlay.DeselectAll; + searchTextBox = modSelectOverlay.SearchTextBox; + selectedMods.BindTo(modSelectOverlay.SelectedMods); } @@ -40,9 +43,8 @@ namespace osu.Game.Overlays.Mods Enabled.Value = selectedMods.Value.Any(); } - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat || e.Action != GlobalAction.DeselectAllMods) + public bool OnPressed(KeyBindingPressEvent e) { + if (e.Repeat || e.Action != GlobalAction.DeselectAllMods || searchTextBox.HasFocus) return false; TriggerClick(); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index cb8eddca62..fdc948bcbf 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -69,16 +69,21 @@ namespace osu.Game.Overlays.Mods /// public string SearchTerm { - get => searchTextBox.Current.Value; + get => SearchTextBox.Current.Value; set { - if (searchTextBox.Current.Value == value) + if (SearchTextBox.Current.Value == value) return; - searchTextBox.Current.Value = value; + SearchTextBox.Current.Value = value; } } + /// + /// Search box applied on mod overlay + /// + public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; + /// /// Whether the total score multiplier calculated from the current selected set of mods should be shown. /// @@ -124,7 +129,6 @@ namespace osu.Game.Overlays.Mods private FillFlowContainer footerButtonFlow = null!; private Container aboveColumnsContent = null!; - private ShearedSearchTextBox searchTextBox = null!; private DifficultyMultiplierDisplay? multiplierDisplay; protected ShearedButton BackButton { get; private set; } = null!; @@ -168,7 +172,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Height = ModsEffectDisplay.HEIGHT, Padding = new MarginPadding { Horizontal = 100 }, - Child = searchTextBox = new ShearedSearchTextBox + Child = SearchTextBox = new ShearedSearchTextBox { HoldFocus = false, Width = 300 @@ -250,8 +254,8 @@ namespace osu.Game.Overlays.Mods { base.Hide(); - //We want to clear search for next user iteraction with mod overlay - searchTextBox.Current.Value = string.Empty; + //We want to clear search for next user interaction with mod overlay + SearchTextBox.Current.Value = string.Empty; } private ModSettingChangeTracker? modSettingChangeTracker; @@ -287,7 +291,7 @@ namespace osu.Game.Overlays.Mods customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); - searchTextBox.Current.BindValueChanged(query => + SearchTextBox.Current.BindValueChanged(query => { foreach (var column in columnFlow.Columns) column.SearchTerm = query.NewValue; From ca688507304721a799c85a40900c1624976954c7 Mon Sep 17 00:00:00 2001 From: Cootz Date: Wed, 10 May 2023 19:43:58 +0300 Subject: [PATCH 17/79] fix formatting --- osu.Game/Overlays/Mods/DeselectAllModsButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs index d4d196508f..bdb37e3ead 100644 --- a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs @@ -43,7 +43,8 @@ namespace osu.Game.Overlays.Mods Enabled.Value = selectedMods.Value.Any(); } - public bool OnPressed(KeyBindingPressEvent e) { + public bool OnPressed(KeyBindingPressEvent e) + { if (e.Repeat || e.Action != GlobalAction.DeselectAllMods || searchTextBox.HasFocus) return false; From 6647d95ea7e80e8430c3daa8cd0bae62c2844c42 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 14 May 2023 18:32:16 +0300 Subject: [PATCH 18/79] Kill search focus when clicking on `ModColumn` --- .../UserInterface/TestSceneModSelectOverlay.cs | 18 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 +++ 2 files changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index d1cbe7d91c..ea81f9c96e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -468,6 +468,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).IsValid); } + [Test] + public void TestSearchFocusChange() + { + createScreen(); + + AddStep("click on search", navigateAndClick); + AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("click on mod column", navigateAndClick); + AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + + void navigateAndClick() where T : Drawable + { + InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().FirstOrDefault()); + InputManager.Click(MouseButton.Left); + } + } + [Test] public void TestDeselectAllViaKey() { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fdc948bcbf..ace5bf71d1 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -777,6 +777,9 @@ namespace osu.Game.Overlays.Mods if (!Active.Value) RequestScroll?.Invoke(this); + //Kill focus on SearchTextBox + Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); + return true; } From 67bf1b4dfe5117cbf1e7a30b1a98caf271f8d637 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 21 May 2023 11:05:01 +0300 Subject: [PATCH 19/79] Select/deselect first visible mod when `GlobalAction.Select` is triggered --- .../UserInterface/TestSceneModSelectOverlay.cs | 18 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 18 ++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index ea81f9c96e..499a30f0dc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -468,6 +468,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).IsValid); } + [Test] + public void TestFirstModSelectDeselect() + { + createScreen(); + + AddStep("apply search", () => modSelectOverlay.SearchTerm = "HD"); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddAssert("hidden selected", () => getPanelForMod(typeof(OsuModHidden)).Active.Value); + + AddStep("press enter again", () => InputManager.Key(Key.Enter)); + AddAssert("hidden deselected", () => !getPanelForMod(typeof(OsuModHidden)).Active.Value); + + AddStep("clear search", () => modSelectOverlay.SearchTerm = string.Empty); + AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); + } + [Test] public void TestSearchFocusChange() { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ace5bf71d1..eb0e797eac 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -612,10 +612,24 @@ namespace osu.Game.Overlays.Mods // This is handled locally here because this overlay is being registered at the game level // and therefore takes away keyboard focus from the screen stack. case GlobalAction.ToggleModSelection: + // Pressing toggle should completely hide the overlay in one shot. + hideOverlay(true); + return true; + case GlobalAction.Select: { - // Pressing toggle or select should completely hide the overlay in one shot. - hideOverlay(true); + // Pressing select should select first filtered mod or completely hide the overlay in one shot if search term is empty. + if (string.IsNullOrEmpty(SearchTerm)) + { + hideOverlay(true); + return true; + } + + ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.IsValid); + + if (firstMod is not null) + firstMod.Active.Value = !firstMod.Active.Value; + return true; } } From 5ff023113fed2c6ef46454e62e9db2f5e8afcc3d Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 28 May 2023 10:04:26 +0300 Subject: [PATCH 20/79] Update xmldoc for `ForcedSearchTerm` --- osu.Game/Overlays/Mods/ModSearchContainer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs index f9bf11d1f1..b959274391 100644 --- a/osu.Game/Overlays/Mods/ModSearchContainer.cs +++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs @@ -10,6 +10,9 @@ namespace osu.Game.Overlays.Mods /// /// A string that should match the children /// + /// + /// Same as except the filtering is guarantied to be performed even when can't be run. + /// public string ForcedSearchTerm { get => SearchTerm; From 0e5c99b760a8e3e2298f5bfb1ac2fb8d82ee83d2 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 28 May 2023 13:12:57 +0300 Subject: [PATCH 21/79] Fix search bar showing incorrectly --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d3a2e001e7..3d42059540 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -185,7 +185,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = (ShowTotalMultiplier ? ModsEffectDisplay.HEIGHT : 0) + PADDING, + Top = ModsEffectDisplay.HEIGHT + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, From e43c233b4879006e3cbd36eb77eaade7b23a183c Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 28 May 2023 13:21:41 +0300 Subject: [PATCH 22/79] Reword `ForcedSearchTerm` xmldoc --- osu.Game/Overlays/Mods/ModSearchContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs index b959274391..132c02db1e 100644 --- a/osu.Game/Overlays/Mods/ModSearchContainer.cs +++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs @@ -8,10 +8,11 @@ namespace osu.Game.Overlays.Mods public partial class ModSearchContainer : SearchContainer { /// - /// A string that should match the children + /// Same as except the filtering is guarantied to be performed /// /// - /// Same as except the filtering is guarantied to be performed even when can't be run. + /// This is required because can be hidden when search term applied + /// therefore cannot be reached and filter cannot automatically re-validate itself. /// public string ForcedSearchTerm { From 22c6d6c5262a97af2f1a129c2a6a6292ed76bad2 Mon Sep 17 00:00:00 2001 From: Cootz Date: Mon, 29 May 2023 14:22:40 +0300 Subject: [PATCH 23/79] Prevent checkbox from toggle on when column have no valid panels --- osu.Game/Overlays/Mods/ModColumn.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 71d964c618..f7d7d4db73 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -151,7 +151,10 @@ namespace osu.Game.Overlays.Mods if (toggleAllCheckbox != null && !SelectionAnimationRunning) { toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.IsValid) ? 1 : 0; - toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.IsValid).All(panel => panel.Active.Value); + + //Prevent checkbox from checking when column have on valid panels + if (availableMods.Any(panel => panel.IsValid)) + toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.IsValid).All(panel => panel.Active.Value); } } From e35623df22874e4dfc3c9f9ad721bebb422aa570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 May 2023 13:38:39 +0900 Subject: [PATCH 24/79] Update to use new `Filter` method and remove silly `ForcedSearchTerm` --- osu.Game/Overlays/Mods/ModSearchContainer.cs | 17 +++++++---------- osu.Game/Overlays/Mods/ModSelectColumn.cs | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs index 132c02db1e..784322b892 100644 --- a/osu.Game/Overlays/Mods/ModSearchContainer.cs +++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs @@ -7,23 +7,20 @@ namespace osu.Game.Overlays.Mods { public partial class ModSearchContainer : SearchContainer { - /// - /// Same as except the filtering is guarantied to be performed - /// - /// - /// This is required because can be hidden when search term applied - /// therefore cannot be reached and filter cannot automatically re-validate itself. - /// - public string ForcedSearchTerm + public new string SearchTerm { - get => SearchTerm; + get => base.SearchTerm; set { if (value == SearchTerm) return; SearchTerm = value; - Update(); + + // Manual filtering here is required because ModColumn can be hidden when search term applied, + // causing the whole SearchContainer to become non-present and never actually perform a subsequent + // filter. + Filter(); } } } diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index c80eb8c09c..338ebdaef4 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Mods public string SearchTerm { - set => ItemsFlow.ForcedSearchTerm = value; + set => ItemsFlow.SearchTerm = value; } protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; From 6e00b21a3200ceb5f416d19ecc6cb41fd62e7b6d Mon Sep 17 00:00:00 2001 From: Cootz Date: Wed, 31 May 2023 19:16:16 +0300 Subject: [PATCH 25/79] Update framework version --- osu.Game/osu.Game.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0fd2b0c2c5..db6d9b07cd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,4 +1,4 @@ - + net6.0 Library @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 5a1c3aeb7e7ef0b735ef8f6fa071afd216f192ca Mon Sep 17 00:00:00 2001 From: Cootz Date: Wed, 31 May 2023 19:36:42 +0300 Subject: [PATCH 26/79] Fix `SearchTerm` set causing infinite loop --- osu.Game/Overlays/Mods/ModSearchContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSearchContainer.cs b/osu.Game/Overlays/Mods/ModSearchContainer.cs index 784322b892..8787530d5c 100644 --- a/osu.Game/Overlays/Mods/ModSearchContainer.cs +++ b/osu.Game/Overlays/Mods/ModSearchContainer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Mods if (value == SearchTerm) return; - SearchTerm = value; + base.SearchTerm = value; // Manual filtering here is required because ModColumn can be hidden when search term applied, // causing the whole SearchContainer to become non-present and never actually perform a subsequent From 39489358fa5f91d56b54e92b4e8c8b939da5b659 Mon Sep 17 00:00:00 2001 From: Cootz Date: Thu, 1 Jun 2023 14:07:05 +0300 Subject: [PATCH 27/79] Apply appearance animation to `aboveColumnsContent` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3d42059540..a491da1f76 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -507,7 +507,7 @@ namespace osu.Game.Overlays.Mods base.PopIn(); - multiplierDisplay? + aboveColumnsContent? .FadeIn(fade_in_duration, Easing.OutQuint) .MoveToY(0, fade_in_duration, Easing.OutQuint); @@ -565,7 +565,7 @@ namespace osu.Game.Overlays.Mods base.PopOut(); - multiplierDisplay? + aboveColumnsContent? .FadeOut(fade_out_duration / 2, Easing.OutQuint) .MoveToY(-distance, fade_out_duration / 2, Easing.OutQuint); From 325c114c1c9ba4b7f1bfa0630c2d1ab0885d1cf1 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 2 Jun 2023 10:39:27 +0300 Subject: [PATCH 28/79] Remove redundant xmldocs --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a491da1f76..af8a7bd810 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -64,9 +64,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// Search term applied on mod overlay - /// public string SearchTerm { get => SearchTextBox.Current.Value; @@ -79,9 +76,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// Search box applied on mod overlay - /// public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; /// From d400387329e9c255e39a8229697ef47aeeb639cf Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 2 Jun 2023 10:51:33 +0300 Subject: [PATCH 29/79] Replace `IConditionalFilterable` with `IFilterable` --- osu.Game/Overlays/Mods/ModPanel.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index cd94226d8f..e3bd3ad370 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public partial class ModPanel : ModSelectPanel, IConditionalFilterable + public partial class ModPanel : ModSelectPanel, IFilterable { public Mod Mod => modState.Mod; public override BindableBool Active => modState.Active; @@ -110,13 +110,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// This property is always because it affects search result - /// - private readonly BindableBool canBeShown = new BindableBool(true); - - IBindable IConditionalFilterable.CanBeShown => canBeShown; - private void updateFilterState() { this.FadeTo(IsValid ? 1 : 0); From 4c7cca101eab9879a7605c604be75c43516f26bf Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 2 Jun 2023 11:33:38 +0300 Subject: [PATCH 30/79] Rename `IsValid` to `Visible` --- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../Visual/UserInterface/TestSceneModColumn.cs | 16 ++++++++-------- .../UserInterface/TestSceneModSelectOverlay.cs | 16 ++++++++-------- .../Mods/Input/ClassicModHotkeyHandler.cs | 2 +- .../Mods/Input/SequentialModHotkeyHandler.cs | 2 +- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- osu.Game/Overlays/Mods/ModPanel.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- osu.Game/Overlays/Mods/ModState.cs | 4 ++-- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 2 +- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 40acea475b..a41eff067b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("mod select contains only double time mod", () => this.ChildrenOfType().Single().UserModsSelectOverlay .ChildrenOfType() - .SingleOrDefault(panel => panel.IsValid)?.Mod is OsuModDoubleTime); + .SingleOrDefault(panel => panel.Visible)?.Mod is OsuModDoubleTime); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 394a38fe5d..255dbfcdd3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -106,26 +106,26 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); - AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.IsValid) == 2); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.Visible) == 2); clickToggle(); AddUntilStep("wait for animation", () => !column.SelectionAnimationRunning); - AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => panel.IsValid)); + AddAssert("only visible items selected", () => column.ChildrenOfType().Where(panel => panel.Active.Value).All(panel => panel.Visible)); AddStep("unset filter", () => setFilter(null)); - AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.IsValid)); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.Visible)); AddAssert("checkbox not selected", () => !column.ChildrenOfType().Single().Current.Value); AddStep("set filter", () => setFilter(mod => mod.Name.Contains("Wind", StringComparison.CurrentCultureIgnoreCase))); - AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.IsValid) == 2); + AddUntilStep("two panels visible", () => column.ChildrenOfType().Count(panel => panel.Visible) == 2); AddAssert("checkbox selected", () => column.ChildrenOfType().Single().Current.Value); AddStep("filter out everything", () => setFilter(_ => false)); - AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.IsValid)); + AddUntilStep("no panels visible", () => column.ChildrenOfType().All(panel => !panel.Visible)); AddUntilStep("checkbox hidden", () => !column.ChildrenOfType().Single().IsPresent); AddStep("inset filter", () => setFilter(null)); - AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.IsValid)); + AddUntilStep("all panels visible", () => column.ChildrenOfType().All(panel => panel.Visible)); AddUntilStep("checkbox visible", () => column.ChildrenOfType().Single().IsPresent); void clickToggle() => AddStep("click toggle", () => @@ -320,14 +320,14 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("search by mod name", () => column.SearchTerm = searchTerm); - AddAssert("only hidden is visible", () => column.ChildrenOfType().Where(panel => panel.IsValid).All(panel => panel.Mod is ModHidden)); + AddAssert("only hidden is visible", () => column.ChildrenOfType().Where(panel => panel.Visible).All(panel => panel.Mod is ModHidden)); } void clearSearch() { AddStep("clear search", () => column.SearchTerm = string.Empty); - AddAssert("all mods are visible", () => column.ChildrenOfType().All(panel => panel.IsValid)); + AddAssert("all mods are visible", () => column.ChildrenOfType().All(panel => panel.Visible)); } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 22baea2581..c42f9af6df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -490,15 +490,15 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); - AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.IsValid)); + AddAssert("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.Visible)); AddStep("make double time invalid", () => modSelectOverlay.IsValidMod = m => !(m is OsuModDoubleTime)); - AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => !panel.IsValid)); - AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => panel.IsValid)); + AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => !panel.Visible)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => panel.Visible)); AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true); - AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.IsValid)); - AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => panel.IsValid)); + AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => panel.Visible)); + AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => panel.Visible)); } [Test] @@ -524,7 +524,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set ruleset", () => Ruleset.Value = testRuleset.RulesetInfo); waitForColumnLoad(); - AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).IsValid); + AddAssert("unimplemented mod panel is filtered", () => !getPanelForMod(typeof(TestUnimplementedMod)).Visible); } [Test] @@ -585,7 +585,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); AddStep("focus on search", () => modSelectOverlay.SearchTextBox.TakeFocus()); AddStep("apply search", () => modSelectOverlay.SearchTerm = "Easy"); - AddAssert("DT + HD selected and hidden", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.IsValid && panel.Active.Value) == 2); + AddAssert("DT + HD selected and hidden", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.Visible && panel.Active.Value) == 2); AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); AddAssert("DT + HD still selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); @@ -630,7 +630,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("apply search", () => modSelectOverlay.SearchTerm = "Easy"); - AddAssert("DT + HD + RD are hidden and selected", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.IsValid && panel.Active.Value) == 3); + AddAssert("DT + HD + RD are hidden and selected", () => modSelectOverlay.ChildrenOfType().Count(panel => !panel.Visible && panel.Active.Value) == 3); AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("click deselect all button", () => diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 343f7242f1..59a631a7b5 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Mods.Input if (!mod_type_lookup.TryGetValue(e.Key, out var typesToMatch)) return false; - var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.IsValid).ToArray(); + var matchingMods = availableMods.Where(modState => matches(modState, typesToMatch) && modState.Visible).ToArray(); if (matchingMods.Length == 0) return false; diff --git a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs index cbfa96307d..e638063438 100644 --- a/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/SequentialModHotkeyHandler.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Mods.Input if (index < 0) return false; - var modState = availableMods.Where(modState => modState.IsValid).ElementAtOrDefault(index); + var modState = availableMods.Where(modState => modState.Visible).ElementAtOrDefault(index); if (modState == null) return false; diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index f7d7d4db73..9146cd7abe 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -146,15 +146,15 @@ namespace osu.Game.Overlays.Mods private void updateState() { - Alpha = availableMods.All(mod => !mod.IsValid) ? 0 : 1; + Alpha = availableMods.All(mod => !mod.Visible) ? 0 : 1; if (toggleAllCheckbox != null && !SelectionAnimationRunning) { - toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.IsValid) ? 1 : 0; + toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.Visible) ? 1 : 0; //Prevent checkbox from checking when column have on valid panels - if (availableMods.Any(panel => panel.IsValid)) - toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.IsValid).All(panel => panel.Active.Value); + if (availableMods.Any(panel => panel.Visible)) + toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.Visible).All(panel => panel.Active.Value); } } @@ -199,7 +199,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in availableMods.Where(b => !b.Active.Value && b.IsValid)) + foreach (var button in availableMods.Where(b => !b.Active.Value && b.Visible)) pendingSelectionOperations.Enqueue(() => button.Active.Value = true); } diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index e3bd3ad370..86ecdfa31d 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -73,9 +73,9 @@ namespace osu.Game.Overlays.Mods } /// - /// Determine if is valid and can be shown + /// Whether the is passing all filters and visible for user /// - public bool IsValid => modState.IsValid; + public bool Visible => modState.Visible; public bool ValidForSelection { @@ -112,7 +112,7 @@ namespace osu.Game.Overlays.Mods private void updateFilterState() { - this.FadeTo(IsValid ? 1 : 0); + this.FadeTo(Visible ? 1 : 0); } #endregion diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index af8a7bd810..1d5849e3f2 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -511,7 +511,7 @@ namespace osu.Game.Overlays.Mods { var column = columnFlow[i].Column; - bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => !modState.IsValid); + bool allFiltered = column is ModColumn modColumn && modColumn.AvailableMods.All(modState => !modState.Visible); double delay = allFiltered ? 0 : nonFilteredColumnCount * 30; double duration = allFiltered ? 0 : fade_in_duration; @@ -573,7 +573,7 @@ namespace osu.Game.Overlays.Mods if (column is ModColumn modColumn) { - allFiltered = modColumn.AvailableMods.All(modState => !modState.IsValid); + allFiltered = modColumn.AvailableMods.All(modState => !modState.Visible); modColumn.FlushPendingSelections(); } @@ -623,7 +623,7 @@ namespace osu.Game.Overlays.Mods return true; } - ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.IsValid); + ModState? firstMod = columnFlow.Columns.OfType().FirstOrDefault(m => m.IsPresent)?.AvailableMods.FirstOrDefault(x => x.Visible); if (firstMod is not null) firstMod.Active.Value = !firstMod.Active.Value; diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs index be770ec937..5e0d768021 100644 --- a/osu.Game/Overlays/Mods/ModState.cs +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -39,9 +39,9 @@ namespace osu.Game.Overlays.Mods public BindableBool ValidForSelection { get; } = new BindableBool(true); /// - /// Determine if is valid and can be shown + /// Whether the is passing all filters and visible for user /// - public bool IsValid => MatchingFilter.Value && ValidForSelection.Value; + public bool Visible => MatchingFilter.Value && ValidForSelection.Value; /// /// Whether the mod is matching the current filter, i.e. it is available for user selection. diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index dad4f7b629..8a6180a7c8 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.Mods { Enabled.Value = availableMods.Value .SelectMany(pair => pair.Value) - .Any(modState => !modState.Active.Value && modState.IsValid); + .Any(modState => !modState.Active.Value && modState.Visible); } public bool OnPressed(KeyBindingPressEvent e) From 9d4ba5d64ab354b8163efc73a6287ec610a32fa9 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 2 Jun 2023 12:00:03 +0300 Subject: [PATCH 31/79] Remove unnecessary null checks --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 1d5849e3f2..ad5d571535 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -501,7 +501,7 @@ namespace osu.Game.Overlays.Mods base.PopIn(); - aboveColumnsContent? + aboveColumnsContent .FadeIn(fade_in_duration, Easing.OutQuint) .MoveToY(0, fade_in_duration, Easing.OutQuint); @@ -559,7 +559,7 @@ namespace osu.Game.Overlays.Mods base.PopOut(); - aboveColumnsContent? + aboveColumnsContent .FadeOut(fade_out_duration / 2, Easing.OutQuint) .MoveToY(-distance, fade_out_duration / 2, Easing.OutQuint); From 69b640a185a1850159064a2701cba7726c27da44 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sat, 3 Jun 2023 10:47:48 +0300 Subject: [PATCH 32/79] Remove hotkeys handling from `DeselectAllModsButton` --- osu.Game/Overlays/Mods/DeselectAllModsButton.cs | 5 +---- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs index bdb37e3ead..3e5a3b12d1 100644 --- a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs @@ -18,7 +18,6 @@ namespace osu.Game.Overlays.Mods public partial class DeselectAllModsButton : ShearedButton, IKeyBindingHandler { private readonly Bindable> selectedMods = new Bindable>(); - private readonly ShearedSearchTextBox searchTextBox; public DeselectAllModsButton(ModSelectOverlay modSelectOverlay) : base(ModSelectOverlay.BUTTON_WIDTH) @@ -26,8 +25,6 @@ namespace osu.Game.Overlays.Mods Text = CommonStrings.DeselectAll; Action = modSelectOverlay.DeselectAll; - searchTextBox = modSelectOverlay.SearchTextBox; - selectedMods.BindTo(modSelectOverlay.SelectedMods); } @@ -45,7 +42,7 @@ namespace osu.Game.Overlays.Mods public bool OnPressed(KeyBindingPressEvent e) { - if (e.Repeat || e.Action != GlobalAction.DeselectAllMods || searchTextBox.HasFocus) + if (e.Repeat || e.Action != GlobalAction.DeselectAllMods) return false; TriggerClick(); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ad5d571535..e3977f6ecd 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -66,17 +66,17 @@ namespace osu.Game.Overlays.Mods public string SearchTerm { - get => SearchTextBox.Current.Value; + get => searchTextBox.Current.Value; set { - if (SearchTextBox.Current.Value == value) + if (searchTextBox.Current.Value == value) return; - SearchTextBox.Current.Value = value; + searchTextBox.Current.Value = value; } } - public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; + private ShearedSearchTextBox searchTextBox = null!; /// /// Whether the total score multiplier calculated from the current selected set of mods should be shown. @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Height = ModsEffectDisplay.HEIGHT, Padding = new MarginPadding { Horizontal = 100 }, - Child = SearchTextBox = new ShearedSearchTextBox + Child = searchTextBox = new ShearedSearchTextBox { HoldFocus = false, Width = 300 @@ -249,7 +249,7 @@ namespace osu.Game.Overlays.Mods base.Hide(); //We want to clear search for next user interaction with mod overlay - SearchTextBox.Current.Value = string.Empty; + searchTextBox.Current.Value = string.Empty; } private ModSettingChangeTracker? modSettingChangeTracker; @@ -289,7 +289,7 @@ namespace osu.Game.Overlays.Mods customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); - SearchTextBox.Current.BindValueChanged(query => + searchTextBox.Current.BindValueChanged(query => { foreach (var column in columnFlow.Columns) column.SearchTerm = query.NewValue; @@ -789,7 +789,7 @@ namespace osu.Game.Overlays.Mods if (!Active.Value) RequestScroll?.Invoke(this); - //Kill focus on SearchTextBox + //Kill focus on searchTextBox Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); return true; From 32b9e6ec8f0a5a395d2295e0510fa3c40a058e67 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 4 Jun 2023 17:02:46 +0300 Subject: [PATCH 33/79] Make search bar active by default --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e3977f6ecd..9d07ee1c93 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -66,17 +66,17 @@ namespace osu.Game.Overlays.Mods public string SearchTerm { - get => searchTextBox.Current.Value; + get => SearchTextBox.Current.Value; set { - if (searchTextBox.Current.Value == value) + if (SearchTextBox.Current.Value == value) return; - searchTextBox.Current.Value = value; + SearchTextBox.Current.Value = value; } } - private ShearedSearchTextBox searchTextBox = null!; + public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; /// /// Whether the total score multiplier calculated from the current selected set of mods should be shown. @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Height = ModsEffectDisplay.HEIGHT, Padding = new MarginPadding { Horizontal = 100 }, - Child = searchTextBox = new ShearedSearchTextBox + Child = SearchTextBox = new ShearedSearchTextBox { HoldFocus = false, Width = 300 @@ -249,7 +249,7 @@ namespace osu.Game.Overlays.Mods base.Hide(); //We want to clear search for next user interaction with mod overlay - searchTextBox.Current.Value = string.Empty; + SearchTextBox.Current.Value = string.Empty; } private ModSettingChangeTracker? modSettingChangeTracker; @@ -289,12 +289,14 @@ namespace osu.Game.Overlays.Mods customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); - searchTextBox.Current.BindValueChanged(query => + SearchTextBox.Current.BindValueChanged(query => { foreach (var column in columnFlow.Columns) column.SearchTerm = query.NewValue; }, true); + SearchTextBox.TakeFocus(); + // Start scrolled slightly to the right to give the user a sense that // there is more horizontal content available. ScheduleAfterChildren(() => @@ -789,7 +791,7 @@ namespace osu.Game.Overlays.Mods if (!Active.Value) RequestScroll?.Invoke(this); - //Kill focus on searchTextBox + //Kill focus on SearchTextBox Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); return true; From fd554033db74781f4a40a25527c91d34d7c79cc3 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 4 Jun 2023 17:11:04 +0300 Subject: [PATCH 34/79] Update tests --- .../Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs | 2 ++ .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 3 +++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 45f671618e..60bd88cc2b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -62,6 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { createFreeModSelect(); + AddStep("kill search bar focus", () => freeModSelectOverlay.SearchTextBox.KillFocus()); + AddStep("press ctrl+a", () => InputManager.Keys(PlatformAction.SelectAll)); AddUntilStep("all mods selected", assertAllAvailableModsSelected); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index c42f9af6df..26369edeb6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Mods; using osuTK; using osuTK.Input; @@ -569,6 +570,8 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); + AddStep("kill search bar focus", () => modSelectOverlay.SearchTextBox.KillFocus()); + AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); From a46f5b90d4c4db173675e080f4aef42f508b9e48 Mon Sep 17 00:00:00 2001 From: Cootz Date: Sun, 4 Jun 2023 18:11:44 +0300 Subject: [PATCH 35/79] Move focus handling into `PopIn` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9d07ee1c93..7380c53073 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -295,8 +295,6 @@ namespace osu.Game.Overlays.Mods column.SearchTerm = query.NewValue; }, true); - SearchTextBox.TakeFocus(); - // Start scrolled slightly to the right to give the user a sense that // there is more horizontal content available. ScheduleAfterChildren(() => @@ -503,6 +501,8 @@ namespace osu.Game.Overlays.Mods base.PopIn(); + SearchTextBox.TakeFocus(); + aboveColumnsContent .FadeIn(fade_in_duration, Easing.OutQuint) .MoveToY(0, fade_in_duration, Easing.OutQuint); From 3ebc8014847ed22b189ab1843ad8ae72bd31e8a3 Mon Sep 17 00:00:00 2001 From: Cootz Date: Mon, 5 Jun 2023 13:49:07 +0300 Subject: [PATCH 36/79] Move (de)select all mods hotkeys handling to `ModSelectOverlay` --- .../TestSceneModSelectOverlay.cs | 1 - .../Overlays/Mods/DeselectAllModsButton.cs | 18 +-------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 37 ++++++++++++++++++- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 18 +-------- .../OnlinePlay/FreeModSelectOverlay.cs | 14 ++++--- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 26369edeb6..37b6a6d44f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Mods; using osuTK; using osuTK.Input; diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs index 3e5a3b12d1..817b6beac3 100644 --- a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs @@ -6,16 +6,13 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; -using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public partial class DeselectAllModsButton : ShearedButton, IKeyBindingHandler + public partial class DeselectAllModsButton : ShearedButton { private readonly Bindable> selectedMods = new Bindable>(); @@ -39,18 +36,5 @@ namespace osu.Game.Overlays.Mods { Enabled.Value = selectedMods.Value.Any(); } - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat || e.Action != GlobalAction.DeselectAllMods) - return false; - - TriggerClick(); - return true; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7380c53073..51e1c33124 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -12,6 +12,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; @@ -28,7 +30,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public abstract partial class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler + public abstract partial class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler, IKeyBindingHandler { public const int BUTTON_WIDTH = 200; @@ -108,7 +110,7 @@ namespace osu.Game.Overlays.Mods }; } - yield return new DeselectAllModsButton(this); + yield return deselectAllModsButton = new DeselectAllModsButton(this); } private readonly Bindable>> globalAvailableMods = new Bindable>>(); @@ -121,12 +123,14 @@ namespace osu.Game.Overlays.Mods private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; private FillFlowContainer footerButtonFlow = null!; + private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; private DifficultyMultiplierDisplay? multiplierDisplay; protected ShearedButton BackButton { get; private set; } = null!; protected ShearedToggleButton? CustomisationButton { get; private set; } + protected SelectAllModsButton? SelectAllModsButton { get; set; } private Sample? columnAppearSample; @@ -616,6 +620,18 @@ namespace osu.Game.Overlays.Mods hideOverlay(true); return true; + //This is handled locally here to prevent search box from coupling in DeselectAllModsButton + case GlobalAction.DeselectAllMods: + { + if (!SearchTextBox.HasFocus) + { + deselectAllModsButton.TriggerClick(); + return true; + } + + break; + } + case GlobalAction.Select: { // Pressing select should select first filtered mod or completely hide the overlay in one shot if search term is empty. @@ -651,6 +667,23 @@ namespace osu.Game.Overlays.Mods } } + /// + /// + /// This is handled locally here to allow handle first + /// > + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton is null) + return false; + + SelectAllModsButton.TriggerClick(); + return true; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + #endregion #region Sample playback control diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index 8a6180a7c8..83c46cfc1f 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -6,9 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; @@ -16,7 +13,7 @@ using osu.Game.Screens.OnlinePlay; namespace osu.Game.Overlays.Mods { - public partial class SelectAllModsButton : ShearedButton, IKeyBindingHandler + public partial class SelectAllModsButton : ShearedButton { private readonly Bindable> selectedMods = new Bindable>(); private readonly Bindable>> availableMods = new Bindable>>(); @@ -46,18 +43,5 @@ namespace osu.Game.Overlays.Mods .SelectMany(pair => pair.Value) .Any(modState => !modState.Active.Value && modState.Visible); } - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat || e.Action != PlatformAction.SelectAll) - return false; - - TriggerClick(); - return true; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } } } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 6313d907a5..d5e57b9ec9 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -34,11 +34,13 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons().Prepend( - new SelectAllModsButton(this) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); + protected override IEnumerable CreateFooterButtons() + => base.CreateFooterButtons() + .Prepend( + SelectAllModsButton = new SelectAllModsButton(this) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); } } From 71e6f80c40e36a3b3f14f8d4ccdb38e707cf6b17 Mon Sep 17 00:00:00 2001 From: Cootz Date: Mon, 5 Jun 2023 15:54:19 +0300 Subject: [PATCH 37/79] Add hotkey for switching search bar focus --- .../TestSceneModSelectOverlay.cs | 22 +++++++++++++++---- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 37b6a6d44f..ffc0a0a0ad 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -546,16 +546,16 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestSearchFocusChange() + public void TestSearchFocusChangeViaClick() { createScreen(); - AddStep("click on search", navigateAndClick); - AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); - AddStep("click on mod column", navigateAndClick); AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + AddStep("click on search", navigateAndClick); + AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + void navigateAndClick() where T : Drawable { InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().FirstOrDefault()); @@ -563,6 +563,20 @@ namespace osu.Game.Tests.Visual.UserInterface } } + [Test] + public void TestSearchFocusChangeViaKey() + { + createScreen(); + + const Key focus_switch_key = Key.Tab; + + AddStep("press tab", () => InputManager.Key(focus_switch_key)); + AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + + AddStep("press tab", () => InputManager.Key(focus_switch_key)); + AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + } + [Test] public void TestDeselectAllViaKey() { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 51e1c33124..3795ed729d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,6 +27,7 @@ using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; +using osuTK.Input; namespace osu.Game.Overlays.Mods { @@ -684,6 +685,19 @@ namespace osu.Game.Overlays.Mods { } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat || e.Key != Key.Tab) + return false; + + if (SearchTextBox.HasFocus) + SearchTextBox.KillFocus(); + else + SearchTextBox.TakeFocus(); + + return true; + } + #endregion #region Sample playback control From ba7069df34b65ee70ecf9962d240c3deb05dc68a Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 6 Jun 2023 16:12:31 +0300 Subject: [PATCH 38/79] =?UTF-8?q?Fix=20`SelectAllModsButton`=20state=20doe?= =?UTF-8?q?sn=E2=80=99t=20update=20when=20search=20term=20changed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TestSceneFreeModSelectOverlay.cs | 24 +++++++++++++++++++ osu.Game/Overlays/Mods/SelectAllModsButton.cs | 3 +++ 2 files changed, 27 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 60bd88cc2b..66ba908879 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -8,6 +8,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Testing; @@ -57,6 +58,29 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0); } + [Test] + public void TestSelectAllButtonUpdatesStateWhenSearchTermChanged() + { + createFreeModSelect(); + + AddStep("apply search term", () => freeModSelectOverlay.SearchTerm = "ea"); + + AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + + AddStep("click select all button", navigateAndClick); + AddAssert("select all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + + AddStep("change search term", () => freeModSelectOverlay.SearchTerm = "e"); + + AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + + void navigateAndClick() where T : Drawable + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + } + } + [Test] public void TestSelectDeselectAllViaKeyboard() { diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index 83c46cfc1f..dd14514a3b 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -17,6 +17,7 @@ namespace osu.Game.Overlays.Mods { private readonly Bindable> selectedMods = new Bindable>(); private readonly Bindable>> availableMods = new Bindable>>(); + private readonly Bindable searchTerm = new Bindable(); public SelectAllModsButton(FreeModSelectOverlay modSelectOverlay) : base(ModSelectOverlay.BUTTON_WIDTH) @@ -26,6 +27,7 @@ namespace osu.Game.Overlays.Mods selectedMods.BindTo(modSelectOverlay.SelectedMods); availableMods.BindTo(modSelectOverlay.AvailableMods); + searchTerm.BindTo(modSelectOverlay.SearchTextBox.Current); } protected override void LoadComplete() @@ -34,6 +36,7 @@ namespace osu.Game.Overlays.Mods selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState)); availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState)); + searchTerm.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState)); updateEnabledState(); } From 78b2e6f3df1a95c83e800258594eae71862f1821 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jun 2023 15:54:22 +0900 Subject: [PATCH 39/79] Add setting to limit distance snapping to current time As discussed in https://github.com/ppy/osu/discussions/23815#discussioncomment-6124116. --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Localisation/EditorStrings.cs | 5 +++++ .../Compose/Components/CircularDistanceSnapGrid.cs | 7 +++++++ .../Edit/Compose/Components/DistanceSnapGrid.cs | 13 +++++++++++++ osu.Game/Screens/Edit/Editor.cs | 6 ++++++ 5 files changed, 33 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 365ad37f4c..193068193a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -178,6 +178,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f); SetDefault(OsuSetting.EditorShowHitMarkers, true); SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true); + SetDefault(OsuSetting.EditorLimitedDistanceSnap, false); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -383,5 +384,6 @@ namespace osu.Game.Configuration SafeAreaConsiderations, ComboColourNormalisationAmount, ProfileCoverExpanded, + EditorLimitedDistanceSnap } } diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 20258b9c35..077bd92d4f 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -109,6 +109,11 @@ namespace osu.Game.Localisation /// public static LocalisableString RotationSnapped(float newRotation) => new TranslatableString(getKey(@"rotation_snapped"), @"{0:0}° (snapped)", newRotation); + /// + /// "Limit distance snap placement to current time" + /// + public static LocalisableString LimitedDistanceSnap => new TranslatableString(getKey(@"limited_distance_snap_grid"), @"Limit distance snap placement to current time"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index d6e4e1f030..2eec833832 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -18,6 +18,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract partial class CircularDistanceSnapGrid : DistanceSnapGrid { + [Resolved] + private EditorClock editorClock { get; set; } + protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null) : base(referenceObject, startPosition, startTime, endTime) { @@ -98,9 +101,13 @@ namespace osu.Game.Screens.Edit.Compose.Components if (travelLength < DistanceBetweenTicks) travelLength = DistanceBetweenTicks; + if (LimitedDistanceSnap.Value) + travelLength = SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime()); + // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed // to allow for snapping at a non-multiplied ratio. float snappedDistance = SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier); + double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance); if (snappedTime > LatestEndTime) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 6092ebc08f..9882f6596f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Layout; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -60,6 +61,18 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + /// + /// When enabled, distance snap should only snap to the current time (as per the editor clock). + /// This is to emulate stable behaviour. + /// + protected Bindable LimitedDistanceSnap; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + LimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); + } + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); protected readonly HitObject ReferenceObject; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bb052b1d22..a5097916cc 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -185,6 +185,7 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; private Bindable editorAutoSeekOnPlacement; + private Bindable editorLimitedDistanceSnap; public Editor(EditorLoader loader = null) { @@ -276,6 +277,7 @@ namespace osu.Game.Screens.Edit editorBackgroundDim = config.GetBindable(OsuSetting.EditorDim); editorHitMarkers = config.GetBindable(OsuSetting.EditorShowHitMarkers); editorAutoSeekOnPlacement = config.GetBindable(OsuSetting.EditorAutoSeekOnPlacement); + editorLimitedDistanceSnap = config.GetBindable(OsuSetting.EditorLimitedDistanceSnap); AddInternal(new OsuContextMenuContainer { @@ -337,6 +339,10 @@ namespace osu.Game.Screens.Edit new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) { State = { BindTarget = editorAutoSeekOnPlacement }, + }, + new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) + { + State = { BindTarget = editorLimitedDistanceSnap }, } } }, From c3b50e130997cd36fa440e4116c66276b5f2685d Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 9 Jun 2023 16:25:04 +0300 Subject: [PATCH 40/79] Move the multiplier back to `TopRight` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3795ed729d..6345916e63 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -219,8 +219,8 @@ namespace osu.Game.Overlays.Mods { aboveColumnsContent.Add(multiplierDisplay = new DifficultyMultiplierDisplay { - Anchor = Anchor.Centre, - Origin = Anchor.Centre + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight }); } From 7697dbe4b3aec9ebe5788d55e0f4027f3bf4e4af Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 9 Jun 2023 16:55:19 +0300 Subject: [PATCH 41/79] Mod panel don't play sound when hidden --- osu.Game/Overlays/Mods/ModColumn.cs | 4 ++-- osu.Game/Overlays/Mods/ModSelectPanel.cs | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 9146cd7abe..363f5c5d0f 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -212,8 +212,8 @@ namespace osu.Game.Overlays.Mods foreach (var button in availableMods.Where(b => b.Active.Value)) { - if (Alpha == 0f) - button.Active.Value = false; //If column is hidden change state manually without any animation + if (!button.Visible) + button.Active.Value = false; //If mod panel is hidden change state manually without any animation else pendingSelectionOperations.Enqueue(() => button.Active.Value = false); } diff --git a/osu.Game/Overlays/Mods/ModSelectPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs index 90663d083c..9d87e1da90 100644 --- a/osu.Game/Overlays/Mods/ModSelectPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -193,6 +193,9 @@ namespace osu.Game.Overlays.Mods if (samplePlaybackDisabled.Value) return; + if (!IsPresent) + return; + if (Active.Value) sampleOn?.Play(); else From d219b5f77f33b443a3a57d91368bae74f658c7a7 Mon Sep 17 00:00:00 2001 From: Cootz Date: Fri, 9 Jun 2023 17:10:13 +0300 Subject: [PATCH 42/79] Reword change focus comment --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 6345916e63..574d18de0e 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -838,7 +838,8 @@ namespace osu.Game.Overlays.Mods if (!Active.Value) RequestScroll?.Invoke(this); - //Kill focus on SearchTextBox + //By doing this we kill the focus on SearchTextBox. + //Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); return true; From 145530035cccad94947aba2e4c8ba78d3c18c817 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 15 Jun 2023 20:00:15 +0900 Subject: [PATCH 43/79] Optimise mania density calculation during beatmap conversion --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 632b7cdcc7..bdc5a00583 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -119,14 +119,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps yield return obj; } - private readonly List prevNoteTimes = new List(max_notes_for_density); + private readonly LimitedCapacityQueue prevNoteTimes = new LimitedCapacityQueue(max_notes_for_density); private double density = int.MaxValue; private void computeDensity(double newNoteTime) { - if (prevNoteTimes.Count == max_notes_for_density) - prevNoteTimes.RemoveAt(0); - prevNoteTimes.Add(newNoteTime); + prevNoteTimes.Enqueue(newNoteTime); if (prevNoteTimes.Count >= 2) density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; From 94b7de4b3f5b131a231aba17c48c75c721884169 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jun 2023 14:01:56 +0900 Subject: [PATCH 44/79] Fix old-new standardised score conversion missing some scores due to not rounding correctly --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 66e64f3f7a..582a656efa 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -182,7 +182,7 @@ namespace osu.Game.Database foreach (var mod in score.Mods) modMultiplier *= mod.ScoreMultiplier; - return (long)((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier); + return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier); } private class FakeHit : HitObject From 1f17f416a4e43112303407998bf09b6b9fe42e32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jun 2023 14:04:18 +0900 Subject: [PATCH 45/79] Force migration of old-new standardised scores to run once more --- osu.Game/Database/RealmAccess.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 48eb2826f5..da4caa42ba 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -77,8 +77,9 @@ namespace osu.Game.Database /// 27 2023-06-06 Added EditorTimestamp to BeatmapInfo. /// 28 2023-06-08 Added IsLegacyScore to ScoreInfo, parsed from replay files. /// 29 2023-06-12 Run migration of old lazer scores to be best-effort in the new scoring number space. No actual realm changes. + /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations. /// - private const int schema_version = 29; + private const int schema_version = 30; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -938,6 +939,7 @@ namespace osu.Game.Database } case 29: + case 30: { var scores = migration.NewRealm .All() From b5de109cb31ab021778677722c959e49d6c6f4ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jun 2023 14:54:19 +0900 Subject: [PATCH 46/79] Fix osu!mania hold notes sometimes looking incorrect after rewind --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 3f91328128..faeb133615 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -254,6 +254,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y; sizingContainer.Height = 1 - yOffset / DrawHeight; } + else + sizingContainer.Height = 1; } protected override void CheckForResult(bool userTriggered, double timeOffset) From ce41ef6e5d3e6163dfbd6d1dfadb53aabfe409d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jun 2023 15:24:30 +0900 Subject: [PATCH 47/79] Move `OrderByTotalScore()` to an extension method --- .../Overlays/BeatmapSet/Scores/ScoresContainer.cs | 5 +---- osu.Game/Scoring/ScoreInfoExtensions.cs | 13 +++++++++++++ osu.Game/Scoring/ScoreManager.cs | 11 ----------- .../OnlinePlay/Playlists/PlaylistsResultsScreen.cs | 2 +- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 5 +---- .../Select/Leaderboards/BeatmapLeaderboard.cs | 7 ++----- 6 files changed, 18 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 6d89313979..b53b7826f3 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -47,9 +47,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores [Resolved] private RulesetStore rulesets { get; set; } - [Resolved] - private ScoreManager scoreManager { get; set; } - private GetScoresRequest getScoresRequest; private CancellationTokenSource loadCancellationSource; @@ -85,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores MD5Hash = apiBeatmap.MD5Hash }; - var scores = scoreManager.OrderByTotalScore(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo))).ToArray(); + var scores = value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).OrderByTotalScore().ToArray(); var topScore = scores.First(); scoreTable.DisplayScores(scores, apiBeatmap.Status.GrantsPerformancePoints()); diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs index 7979ca8aaa..e15ab0d34c 100644 --- a/osu.Game/Scoring/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/ScoreInfoExtensions.cs @@ -3,6 +3,8 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; namespace osu.Game.Scoring @@ -13,5 +15,16 @@ namespace osu.Game.Scoring /// A user-presentable display title representing this score. /// public static string GetDisplayTitle(this IScoreInfo scoreInfo) => $"{scoreInfo.User.Username} playing {scoreInfo.Beatmap.GetDisplayTitle()}"; + + /// + /// Orders an array of s by total score. + /// + /// The array of s to reorder. + /// The given ordered by decreasing total score. + public static IEnumerable OrderByTotalScore(this IEnumerable scores) + => scores.OrderByDescending(s => s.TotalScore) + .ThenBy(s => s.OnlineID) + // Local scores may not have an online ID. Fall back to date in these cases. + .ThenBy(s => s.Date); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d5509538fd..9ba7339a31 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -69,17 +69,6 @@ namespace osu.Game.Scoring return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); } - /// - /// Orders an array of s by total score. - /// - /// The array of s to reorder. - /// The given ordered by decreasing total score. - public IEnumerable OrderByTotalScore(IEnumerable scores) - => scores.OrderByDescending(s => s.TotalScore) - .ThenBy(s => s.OnlineID) - // Local scores may not have an online ID. Fall back to date in these cases. - .ThenBy(s => s.Date); - /// /// Retrieves a bindable that represents the total score of a . /// diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index d40d43cd54..aa72394ac9 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -182,7 +182,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// An optional pivot around which the scores were retrieved. private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) => Schedule(() => { - var scoreInfos = scoreManager.OrderByTotalScore(scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo))).ToArray(); + var scoreInfos = scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); // Select a score if we don't already have one selected. // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index a57a8b0f27..7c632b63db 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -29,9 +29,6 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private RealmAccess realm { get; set; } = null!; - [Resolved] - private ScoreManager scoreManager { get; set; } = null!; - [Resolved] private IAPIProvider api { get; set; } = null!; @@ -78,7 +75,7 @@ namespace osu.Game.Screens.Select.Carousel if (changes?.HasCollectionChanges() == false) return; - ScoreInfo? topScore = scoreManager.OrderByTotalScore(sender.Detach()).FirstOrDefault(); + ScoreInfo? topScore = sender.Detach().OrderByTotalScore().FirstOrDefault(); updateable.Rank = topScore?.Rank; updateable.Alpha = topScore != null ? 1 : 0; diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2b40b9faf8..4c41ed3622 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -67,9 +67,6 @@ namespace osu.Game.Screens.Select.Leaderboards } } - [Resolved] - private ScoreManager scoreManager { get; set; } = null!; - [Resolved] private IBindable ruleset { get; set; } = null!; @@ -164,7 +161,7 @@ namespace osu.Game.Screens.Select.Leaderboards return; SetScores( - scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), + response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).OrderByTotalScore(), response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) ); }); @@ -222,7 +219,7 @@ namespace osu.Game.Screens.Select.Leaderboards scores = scores.Where(s => selectedMods.SetEquals(s.Mods.Select(m => m.Acronym))); } - scores = scoreManager.OrderByTotalScore(scores.Detach()); + scores = scores.Detach().OrderByTotalScore(); SetScores(scores); } From 362aa4b3763223c3294b23029eb6284d45525ad6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jun 2023 15:26:13 +0900 Subject: [PATCH 48/79] Also move `GetMaxAchievableCombo` --- osu.Game/Scoring/ScoreInfoExtensions.cs | 10 ++++++++-- osu.Game/Scoring/ScoreManager.cs | 7 ------- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfoExtensions.cs b/osu.Game/Scoring/ScoreInfoExtensions.cs index e15ab0d34c..85598076d6 100644 --- a/osu.Game/Scoring/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/ScoreInfoExtensions.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring { @@ -26,5 +25,12 @@ namespace osu.Game.Scoring .ThenBy(s => s.OnlineID) // Local scores may not have an online ID. Fall back to date in these cases. .ThenBy(s => s.Date); + + /// + /// Retrieves the maximum achievable combo for the provided score. + /// + /// The to compute the maximum achievable combo for. + /// The maximum achievable combo. + public static int GetMaximumAchievableCombo(this ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9ba7339a31..55bcb9f79d 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -89,13 +89,6 @@ namespace osu.Game.Scoring /// The bindable containing the formatted total score string. public Bindable GetBindableTotalScoreString([NotNull] ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score)); - /// - /// Retrieves the maximum achievable combo for the provided score. - /// - /// The to compute the maximum achievable combo for. - /// The maximum achievable combo. - public int GetMaximumAchievableCombo([NotNull] ScoreInfo score) => score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value); - /// /// Provides the total score of a . Responds to changes in the currently-selected . /// diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index fe74c1ba0d..82c429798e 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Ranking.Expanded var topStatistics = new List { new AccuracyStatistic(score.Accuracy), - new ComboStatistic(score.MaxCombo, scoreManager.GetMaximumAchievableCombo(score)), + new ComboStatistic(score.MaxCombo, score.GetMaximumAchievableCombo()), new PerformanceStatistic(score), }; From 36954e55ad395b99673d15ee68eba42800fa8362 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Jun 2023 17:15:45 +0900 Subject: [PATCH 49/79] Fix incorrect mapping when distance spacing is not 1.0x --- .../Compose/Components/CircularDistanceSnapGrid.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 2eec833832..63886e38eb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -101,12 +101,11 @@ namespace osu.Game.Screens.Edit.Compose.Components if (travelLength < DistanceBetweenTicks) travelLength = DistanceBetweenTicks; - if (LimitedDistanceSnap.Value) - travelLength = SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime()); - - // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed - // to allow for snapping at a non-multiplied ratio. - float snappedDistance = SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier); + float snappedDistance = LimitedDistanceSnap.Value + ? SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime()) + // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed + // to allow for snapping at a non-multiplied ratio. + : SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier); double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance); From 28696f595fbd601b18808af1c5dba47c7eb764ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Jun 2023 16:24:07 +0200 Subject: [PATCH 50/79] Privatise setter --- osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9882f6596f..8aa2fa9f45 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// When enabled, distance snap should only snap to the current time (as per the editor clock). /// This is to emulate stable behaviour. /// - protected Bindable LimitedDistanceSnap; + protected Bindable LimitedDistanceSnap { get; private set; } [BackgroundDependencyLoader] private void load(OsuConfigManager config) From a62b11606e75b579fa2086b5f191c251f54b41f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jun 2023 01:32:41 +0900 Subject: [PATCH 51/79] Attempt to fix NaN fps display The only thing I can see which could cause this is reading from the `drawClock.ElapsedFrameTime` after the `isSpike` read causing a div-by-zero. Reading the values once at the start should avoid this. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 9dbeba6449..c1ef573848 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -167,9 +167,12 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); + double elapsedDrawFrameTime = drawClock.ElapsedFrameTime; + double elapsedUpdateFrameTime = updateClock.ElapsedFrameTime; + // If the game goes into a suspended state (ie. debugger attached or backgrounded on a mobile device) // we want to ignore really long periods of no processing. - if (updateClock.ElapsedFrameTime > 10000) + if (elapsedUpdateFrameTime > 10000) return; mainContent.Width = Math.Max(mainContent.Width, counters.DrawWidth); @@ -178,17 +181,17 @@ namespace osu.Game.Graphics.UserInterface // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && elapsedUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && elapsedDrawFrameTime > spike_time_ms; const float damp_time = 100; - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : damp_time, updateClock.ElapsedFrameTime); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, elapsedUpdateFrameTime, hasUpdateSpike ? 0 : damp_time, elapsedUpdateFrameTime); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - displayedFpsCount = 1000 / drawClock.ElapsedFrameTime; + displayedFpsCount = 1000 / elapsedDrawFrameTime; else displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, damp_time, Time.Elapsed); From 3b1f92d8b84f081738d7b660aaa03910ac8fcecc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Jun 2023 01:37:09 +0900 Subject: [PATCH 52/79] Fix fix logic causing further regression on release --- .../Objects/Drawables/DrawableHoldNote.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index faeb133615..a8563d65c4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -242,17 +242,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2; bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2; - // As the note is being held, adjust the size of the sizing container. This has two effects: - // 1. The contained masking container will mask the body and ticks. - // 2. The head note will move along with the new "head position" in the container. - // - // As per stable, this should not apply for early hits, waiting until the object starts to touch the - // judgement area first. - if (Head.IsHit && releaseTime == null && DrawHeight > 0 && Time.Current >= HitObject.StartTime) + if (Time.Current >= HitObject.StartTime) { - // How far past the hit target this hold note is. - float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y; - sizingContainer.Height = 1 - yOffset / DrawHeight; + // As the note is being held, adjust the size of the sizing container. This has two effects: + // 1. The contained masking container will mask the body and ticks. + // 2. The head note will move along with the new "head position" in the container. + // + // As per stable, this should not apply for early hits, waiting until the object starts to touch the + // judgement area first. + if (Head.IsHit && releaseTime == null && DrawHeight > 0) + { + // How far past the hit target this hold note is. + float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y; + sizingContainer.Height = 1 - yOffset / DrawHeight; + } } else sizingContainer.Height = 1; From f70342bd6772cefbfcd389dc30b780116c6ed8d3 Mon Sep 17 00:00:00 2001 From: Maksim Kan Date: Tue, 13 Jun 2023 18:18:46 +0300 Subject: [PATCH 53/79] Fix Triangle skin colors with Dual Stage mod --- .../Skinning/Default/ManiaTrianglesSkinTransformer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs index eb51179cea..3e0fe8ed4b 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Default/ManiaTrianglesSkinTransformer.cs @@ -35,10 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default var stage = beatmap.GetStageForColumnIndex(column); - if (stage.IsSpecialColumn(column)) + int columnInStage = column % stage.Columns; + + if (stage.IsSpecialColumn(columnInStage)) return SkinUtils.As(new Bindable(colourSpecial)); - int distanceToEdge = Math.Min(column, (stage.Columns - 1) - column); + int distanceToEdge = Math.Min(columnInStage, (stage.Columns - 1) - columnInStage); return SkinUtils.As(new Bindable(distanceToEdge % 2 == 0 ? colourOdd : colourEven)); } } From 688e65475dd938544924ba2d7e06355b50fb4bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Jun 2023 10:25:09 +0200 Subject: [PATCH 54/79] Add better test coverage of dual stages in skinnable tests --- .../Skinning/TestScenePlayfield.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs index f85e303940..6485cbb76b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osuTK; namespace osu.Game.Rulesets.Mania.Tests.Skinning { @@ -25,22 +26,35 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning new StageDefinition(2) }; - SetContents(_ => new ManiaPlayfield(stageDefinitions)); + SetContents(_ => new ManiaInputManager(new ManiaRuleset().RulesetInfo, 2) + { + Child = new ManiaPlayfield(stageDefinitions) + }); }); } - [Test] - public void TestDualStages() + [TestCase(2)] + [TestCase(3)] + [TestCase(5)] + public void TestDualStages(int columnCount) { AddStep("create stage", () => { stageDefinitions = new List { - new StageDefinition(2), - new StageDefinition(2) + new StageDefinition(columnCount), + new StageDefinition(columnCount) }; - SetContents(_ => new ManiaPlayfield(stageDefinitions)); + SetContents(_ => new ManiaInputManager(new ManiaRuleset().RulesetInfo, (int)PlayfieldType.Dual + 2 * columnCount) + { + Child = new ManiaPlayfield(stageDefinitions) + { + // bit of a hack to make sure the dual stages fit on screen without overlapping each other. + Size = new Vector2(1.5f), + Scale = new Vector2(1 / 1.5f) + } + }); }); } From 4919069ea6d115c49b0b7993016d92110e350974 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Jun 2023 01:58:02 +0900 Subject: [PATCH 55/79] Avoid humanizer regex compilation overhead when opening song select for the first time --- osu.Game/Screens/Select/SongSelect.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4d6a5398c5..91799dabf0 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -864,7 +863,7 @@ namespace osu.Game.Screens.Select { // Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918 // but also in this case we want support for formatting a number within a string). - FilterControl.InformationalText = $"{"match".ToQuantity(Carousel.CountDisplayed, "#,0")}"; + FilterControl.InformationalText = Carousel.CountDisplayed != 1 ? $"{Carousel.CountDisplayed} matches" : $"{Carousel.CountDisplayed} match"; } private bool boundLocalBindables; From 25fa4a2eb53b00fcb66a6fb2ce4f5fc5dc7d8082 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Jun 2023 19:57:08 +0300 Subject: [PATCH 56/79] Move `DragDrop` handling to base game implementation for iOS support --- osu.Desktop/OsuGameDesktop.cs | 45 -------------------------------- osu.Game/OsuGame.cs | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index d92fea27bf..21cea3ba76 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Versioning; -using System.Threading.Tasks; using Microsoft.Win32; using osu.Desktop.Security; using osu.Framework.Platform; @@ -17,7 +15,6 @@ using osu.Framework; using osu.Framework.Logging; using osu.Game.Updater; using osu.Desktop.Windows; -using osu.Framework.Threading; using osu.Game.IO; using osu.Game.IPC; using osu.Game.Utils; @@ -138,52 +135,10 @@ namespace osu.Desktop desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.Title = Name; - desktopWindow.DragDrop += f => - { - // on macOS, URL associations are handled via SDL_DROPFILE events. - if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) - { - HandleLink(f); - return; - } - - fileDrop(new[] { f }); - }; } protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo(); - private readonly List importableFiles = new List(); - private ScheduledDelegate? importSchedule; - - private void fileDrop(string[] filePaths) - { - lock (importableFiles) - { - importableFiles.AddRange(filePaths); - - Logger.Log($"Adding {filePaths.Length} files for import"); - - // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms. - // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch. - importSchedule?.Cancel(); - importSchedule = Scheduler.AddDelayed(handlePendingImports, 100); - } - } - - private void handlePendingImports() - { - lock (importableFiles) - { - Logger.Log($"Handling batch import of {importableFiles.Count} files"); - - string[] paths = importableFiles.ToArray(); - importableFiles.Clear(); - - Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); - } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3768dad370..a80639d4ff 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -28,6 +29,7 @@ using osu.Framework.Input.Events; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -281,6 +283,52 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + private readonly List dragDropFiles = new List(); + private ScheduledDelegate dragDropImportSchedule; + + public override void SetHost(GameHost host) + { + base.SetHost(host); + + if (host.Window is SDL2Window sdlWindow) + { + sdlWindow.DragDrop += path => + { + // on macOS/iOS, URL associations are handled via SDL_DROPFILE events. + if (path.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal)) + { + HandleLink(path); + return; + } + + lock (dragDropFiles) + { + dragDropFiles.Add(path); + + Logger.Log($@"Adding ""{Path.GetFileName(path)}"" for import"); + + // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms. + // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch. + dragDropImportSchedule?.Cancel(); + dragDropImportSchedule = Scheduler.AddDelayed(handlePendingDragDropImports, 100); + } + }; + } + } + + private void handlePendingDragDropImports() + { + lock (dragDropFiles) + { + Logger.Log($"Handling batch import of {dragDropFiles.Count} files"); + + string[] paths = dragDropFiles.ToArray(); + dragDropFiles.Clear(); + + Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); + } + } + [BackgroundDependencyLoader] private void load() { From eafd774044faad4030a4f4d19a4c26d03e13f2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Jun 2023 20:03:24 +0200 Subject: [PATCH 57/79] Bring back old formatting spec --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 91799dabf0..47e5325baf 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -863,7 +863,7 @@ namespace osu.Game.Screens.Select { // Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918 // but also in this case we want support for formatting a number within a string). - FilterControl.InformationalText = Carousel.CountDisplayed != 1 ? $"{Carousel.CountDisplayed} matches" : $"{Carousel.CountDisplayed} match"; + FilterControl.InformationalText = Carousel.CountDisplayed != 1 ? $"{Carousel.CountDisplayed:#,0} matches" : $"{Carousel.CountDisplayed:#,0} match"; } private bool boundLocalBindables; From fdebf93ae41a87010b973a4ec73bbf9312e8b99b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 17 Jun 2023 14:55:27 -0700 Subject: [PATCH 58/79] Fix incorrect xmldoc of `IBeatmapInfo.Length` --- osu.Game/Beatmaps/IBeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 4f2c08f63d..b8c69cc525 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps IBeatmapSetInfo? BeatmapSet { get; } /// - /// The playable length in milliseconds of this beatmap. + /// The total length in milliseconds of this beatmap. /// double Length { get; } From 9ae864c219c1bed9f6777383f9ddcbf13fca4019 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 17 Jun 2023 15:00:32 -0700 Subject: [PATCH 59/79] Fix beatmap info length tooltip not showing actual drain length --- .../Visual/Online/TestSceneBeatmapSetOverlay.cs | 1 + osu.Game/Beatmaps/IBeatmapOnlineInfo.cs | 5 +++++ osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 10 ++++++++++ osu.Game/Overlays/BeatmapSet/BasicStats.cs | 8 ++++---- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index a27c4ddad2..d9763ef6c8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -86,6 +86,7 @@ namespace osu.Game.Tests.Visual.Online StarRating = 9.99, DifficultyName = @"TEST", Length = 456000, + HitLength = 400000, RulesetID = 3, CircleSize = 1, DrainRate = 2.3f, diff --git a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs index e1634e7d24..707a0696ba 100644 --- a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs @@ -59,5 +59,10 @@ namespace osu.Game.Beatmaps int PassCount { get; } APIFailTimes? FailTimes { get; } + + /// + /// The playable length in milliseconds of this beatmap. + /// + double HitLength { get; } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 7d6740ee46..902b651be9 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -63,6 +63,16 @@ namespace osu.Game.Online.API.Requests.Responses set => Length = TimeSpan.FromSeconds(value).TotalMilliseconds; } + [JsonIgnore] + public double HitLength { get; set; } + + [JsonProperty(@"hit_length")] + private double hitLengthInSeconds + { + get => TimeSpan.FromMilliseconds(HitLength).TotalSeconds; + set => HitLength = TimeSpan.FromSeconds(value).TotalMilliseconds; + } + [JsonProperty(@"convert")] public bool Convert { get; set; } diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 4a9a3d8089..3cc655d561 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -68,13 +68,13 @@ namespace osu.Game.Overlays.BeatmapSet } else { - length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration()); length.Value = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration(); - var onlineInfo = beatmapInfo as IBeatmapOnlineInfo; + if (beatmapInfo is not IBeatmapOnlineInfo onlineInfo) return; - circleCount.Value = (onlineInfo?.CircleCount ?? 0).ToLocalisableString(@"N0"); - sliderCount.Value = (onlineInfo?.SliderCount ?? 0).ToLocalisableString(@"N0"); + circleCount.Value = onlineInfo.CircleCount.ToLocalisableString(@"N0"); + sliderCount.Value = onlineInfo.SliderCount.ToLocalisableString(@"N0"); + length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(onlineInfo.HitLength).ToFormattedDuration()); } } From eb31fdecee97c8551788aa6422aac492357230fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jun 2023 13:48:56 +0900 Subject: [PATCH 60/79] Apply osu! side changes in line with `FocusedOverlayContainer.PopIn` `abstract` change See https://github.com/ppy/osu-framework/pull/5834 --- .../Visual/UserInterface/TestSceneOverlayContainer.cs | 4 ++++ osu.Game/Collections/ManageCollectionsDialog.cs | 2 -- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 1 - osu.Game/Overlays/AccountCreationOverlay.cs | 1 - osu.Game/Overlays/ChatOverlay.cs | 2 -- osu.Game/Overlays/DialogOverlay.cs | 1 - osu.Game/Overlays/LoginOverlay.cs | 2 -- osu.Game/Overlays/MedalOverlay.cs | 6 +++++- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 1 - osu.Game/Overlays/NotificationOverlay.cs | 2 -- osu.Game/Overlays/NowPlayingOverlay.cs | 2 -- osu.Game/Overlays/SettingsPanel.cs | 2 -- osu.Game/Overlays/WaveOverlayContainer.cs | 2 -- .../OnlinePlay/Match/Components/RoomSettingsOverlay.cs | 2 -- osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs | 2 -- 15 files changed, 9 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs index d9c2774611..bb94912c83 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs @@ -101,6 +101,10 @@ namespace osu.Game.Tests.Visual.UserInterface }, }; } + + protected override void PopIn() + { + } } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 36142cf26f..31016b807b 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -114,8 +114,6 @@ namespace osu.Game.Collections protected override void PopIn() { - base.PopIn(); - lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); this.FadeIn(enter_duration, Easing.OutQuint); this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 07b5b53e0e..f92cfc2306 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -152,7 +152,6 @@ namespace osu.Game.Graphics.Containers protected override void PopOut() { - base.PopOut(); previewTrackManager.StopAnyPlaying(this); } diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 6f79316670..ef2e055eae 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -90,7 +90,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); this.FadeIn(transition_time, Easing.OutQuint); if (welcomeScreen.GetChildScreen() != null) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 96dbfe31f3..87df08ceec 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -276,8 +276,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); - this.MoveToY(0, transition_length, Easing.OutQuint); this.FadeIn(transition_length, Easing.OutQuint); } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 098a5d0a33..005162bbcc 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -99,7 +99,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); } diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 536811dfcf..8b60024682 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -75,8 +75,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); - panel.Bounding = true; this.FadeIn(transition_time, Easing.OutQuint); diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index bd895fe6bf..eba35ec6f9 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -246,9 +246,13 @@ namespace osu.Game.Overlays } } + protected override void PopIn() + { + this.FadeIn(200); + } + protected override void PopOut() { - base.PopOut(); this.FadeOut(200); } diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 7f7b09a62c..a372ec70db 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -130,7 +130,6 @@ namespace osu.Game.Overlays.Mods { const double fade_in_duration = 400; - base.PopIn(); this.FadeIn(fade_in_duration, Easing.OutQuint); Header.MoveToY(0, fade_in_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f2eefb6e4b..15e6c94b34 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -206,8 +206,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); - this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); mainContent.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index e3e3b4bd80..15eefb2d9f 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -229,8 +229,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); - this.FadeIn(transition_length, Easing.OutQuint); dragContainer.ScaleTo(1, transition_length, Easing.OutElastic); } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 1681187f82..d7f39a9d8f 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -163,8 +163,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); - ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); SectionsContainer.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 00474cc0d8..34fbec93b7 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -34,8 +34,6 @@ namespace osu.Game.Overlays protected override void PopIn() { - base.PopIn(); - Waves.Show(); this.FadeIn(100, Easing.OutQuint); } diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 4d4fe4ea56..05232fe0e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -54,14 +54,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected override void PopIn() { - base.PopIn(); Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint); Settings.FadeIn(TRANSITION_DURATION / 2); } protected override void PopOut() { - base.PopOut(); Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine); Settings.Delay(TRANSITION_DURATION / 2).FadeOut(TRANSITION_DURATION / 2); } diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index c92dc2e343..5753c268d9 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -86,8 +86,6 @@ namespace osu.Game.Screens.Select.Options protected override void PopIn() { - base.PopIn(); - this.FadeIn(transition_duration, Easing.OutQuint); if (buttonsContainer.Position.X == 1 || Alpha == 0) From d9c00fc4be6aeb7fba570ccee75e39095d0363f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Jun 2023 20:57:43 +0900 Subject: [PATCH 61/79] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index f4d08e443c..522d28dca7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -11,7 +11,7 @@ manifestmerger.jar - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e08b09aef9..4b9f37270b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9aafec6c50..96396ca4ad 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -16,6 +16,6 @@ iossimulator-x64 - + From 62f01e4f4008421280925c7bd26608f084c10ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 13:49:29 +0200 Subject: [PATCH 62/79] Rename `ModState` members to better convey what's what --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModPanel.cs | 8 ++++---- osu.Game/Overlays/Mods/ModState.cs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 610fd4e935..0845edf7f8 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Mods foreach (var mod in availableMods) { mod.Active.BindValueChanged(_ => updateState()); - mod.MatchingFilter.BindValueChanged(_ => updateState()); + mod.MatchingTextFilter.BindValueChanged(_ => updateState()); mod.ValidForSelection.BindValueChanged(_ => updateState()); } diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 86ecdfa31d..3f85e0b5eb 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); modState.ValidForSelection.BindValueChanged(_ => updateFilterState()); - modState.MatchingFilter.BindValueChanged(_ => updateFilterState(), true); + modState.MatchingTextFilter.BindValueChanged(_ => updateFilterState(), true); } protected override void Select() @@ -100,13 +100,13 @@ namespace osu.Game.Overlays.Mods public override bool MatchingFilter { - get => modState.MatchingFilter.Value; + get => modState.MatchingTextFilter.Value; set { - if (modState.MatchingFilter.Value == value) + if (modState.MatchingTextFilter.Value == value) return; - modState.MatchingFilter.Value = value; + modState.MatchingTextFilter.Value = value; } } diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs index 5e0d768021..1ec517ca11 100644 --- a/osu.Game/Overlays/Mods/ModState.cs +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -39,14 +39,14 @@ namespace osu.Game.Overlays.Mods public BindableBool ValidForSelection { get; } = new BindableBool(true); /// - /// Whether the is passing all filters and visible for user + /// Whether the mod is matching the current textual filter. /// - public bool Visible => MatchingFilter.Value && ValidForSelection.Value; + public BindableBool MatchingTextFilter { get; } = new BindableBool(true); /// - /// Whether the mod is matching the current filter, i.e. it is available for user selection. + /// Whether the matches all applicable filters and visible for the user to select. /// - public BindableBool MatchingFilter { get; } = new BindableBool(true); + public bool Visible => MatchingTextFilter.Value && ValidForSelection.Value; public ModState(Mod mod) { From c7e89905767a6cffc6437eece94ddde56d8c2365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 13:51:03 +0200 Subject: [PATCH 63/79] Remove unused property --- osu.Game/Overlays/Mods/ModPanel.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 3f85e0b5eb..829a0886c3 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -77,18 +77,6 @@ namespace osu.Game.Overlays.Mods /// public bool Visible => modState.Visible; - public bool ValidForSelection - { - get => modState.ValidForSelection.Value; - set - { - if (modState.ValidForSelection.Value == value) - return; - - modState.ValidForSelection.Value = value; - } - } - #region Filtering support public override IEnumerable FilterTerms => new[] From 1b4d7db1e6254d55683ae9bbb14a7313a82cb088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 13:51:13 +0200 Subject: [PATCH 64/79] Remove redundant guard `Bindable` has one of those already. --- osu.Game/Overlays/Mods/ModPanel.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 829a0886c3..14e5040767 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -89,13 +89,7 @@ namespace osu.Game.Overlays.Mods public override bool MatchingFilter { get => modState.MatchingTextFilter.Value; - set - { - if (modState.MatchingTextFilter.Value == value) - return; - - modState.MatchingTextFilter.Value = value; - } + set => modState.MatchingTextFilter.Value = value; } private void updateFilterState() From a1015b4145502fc5951548f2bdcd08de857f13e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 13:59:02 +0200 Subject: [PATCH 65/79] Remove duplicated xmldoc and move into relevant region --- osu.Game/Overlays/Mods/ModPanel.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 14e5040767..f294b1892d 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -72,13 +72,11 @@ namespace osu.Game.Overlays.Mods Active.Value = false; } - /// - /// Whether the is passing all filters and visible for user - /// - public bool Visible => modState.Visible; - #region Filtering support + /// + public bool Visible => modState.Visible; + public override IEnumerable FilterTerms => new[] { Mod.Name, From 64e96c6d82772e19a4a8cc8733ef8a560eeb5ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:03:26 +0200 Subject: [PATCH 66/79] Fix duplicate linq and reword comment --- osu.Game/Overlays/Mods/ModColumn.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 0845edf7f8..60c1282a65 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -150,10 +150,12 @@ namespace osu.Game.Overlays.Mods if (toggleAllCheckbox != null && !SelectionAnimationRunning) { - toggleAllCheckbox.Alpha = availableMods.Any(panel => panel.Visible) ? 1 : 0; + bool anyPanelsVisible = availableMods.Any(panel => panel.Visible); - //Prevent checkbox from checking when column have on valid panels - if (availableMods.Any(panel => panel.Visible)) + toggleAllCheckbox.Alpha = anyPanelsVisible ? 1 : 0; + + // checking `anyPanelsVisible` is important since `.All()` returns `true` for empty enumerables. + if (anyPanelsVisible) toggleAllCheckbox.Current.Value = availableMods.Where(panel => panel.Visible).All(panel => panel.Active.Value); } } From 4c78144d10093b4ee3dcd718d06e7ca8b4b66e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:04:00 +0200 Subject: [PATCH 67/79] Remove obvious comment --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 60c1282a65..d65c94d14d 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -215,7 +215,7 @@ namespace osu.Game.Overlays.Mods foreach (var button in availableMods.Where(b => b.Active.Value)) { if (!button.Visible) - button.Active.Value = false; //If mod panel is hidden change state manually without any animation + button.Active.Value = false; else pendingSelectionOperations.Enqueue(() => button.Active.Value = false); } From 9758e5f840c88b3dc39745a4bd8f7841ef9fcc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:16:31 +0200 Subject: [PATCH 68/79] Fix utterly broken test - Was on wrong ruleset, so the mod/free mod sets did literally nothing - `assertHasFreeModButton` had a param that did nothing - Was checking `MatchingFilter` rather than `Visible` --- .../TestSceneMultiplayerMatchSongSelect.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 23090e9da4..947b7e5be6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -94,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible. public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod) { + AddStep("change ruleset", () => Ruleset.Value = new OsuRuleset().RulesetInfo); AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) }); AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); @@ -102,17 +103,17 @@ namespace osu.Game.Tests.Visual.Multiplayer // A previous test's mod overlay could still be fading out. AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType().Count() == 1); - assertHasFreeModButton(allowedMod, false); - assertHasFreeModButton(requiredMod, false); + assertFreeModNotShown(allowedMod); + assertFreeModNotShown(requiredMod); } - private void assertHasFreeModButton(Type type, bool hasButton = true) + private void assertFreeModNotShown(Type type) { - AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay", + AddAssert($"{type.ReadableName()} not displayed in freemod overlay", () => this.ChildrenOfType() .Single() .ChildrenOfType() - .Where(panel => panel.MatchingFilter) + .Where(panel => panel.Visible) .All(b => b.Mod.GetType() != type)); } From 76f509a1db5d57601595b3c050d3ba9458e3383e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:19:38 +0200 Subject: [PATCH 69/79] Do not use `?? true` pattern Universally disliked. `!= false` is preferred. --- osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs index 255dbfcdd3..18739c0275 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs @@ -334,7 +334,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void setFilter(Func? filter) { foreach (var modState in this.ChildrenOfType().Single().AvailableMods) - modState.ValidForSelection.Value = filter?.Invoke(modState.Mod) ?? true; + modState.ValidForSelection.Value = filter?.Invoke(modState.Mod) != false; } private partial class TestModColumn : ModColumn From b9156b1df312789c17b8815120e84b19f096784b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:24:35 +0200 Subject: [PATCH 70/79] Reword/rename some stuff in test --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index ffc0a0a0ad..868ee2c73c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -678,10 +678,10 @@ namespace osu.Game.Tests.Visual.UserInterface } /// - /// Internal search applies from code by setting + /// Covers columns hiding/unhiding on changes of . /// [Test] - public void TestColumnHidingOnInternalSearch() + public void TestColumnHidingOnIsValidChange() { AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay { @@ -711,10 +711,10 @@ namespace osu.Game.Tests.Visual.UserInterface } /// - /// External search applies by user by entering search term into search bar + /// Covers columns hiding/unhiding on changes of . /// [Test] - public void TestColumnHidingOnExternalSearch() + public void TestColumnHidingOnTextFilterChange() { AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay { @@ -738,7 +738,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestHidingOverlayClearsSearch() + public void TestHidingOverlayClearsTextSearch() { AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay { From 28f929dc4db740c5037c99d77f36791927a36f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:28:26 +0200 Subject: [PATCH 71/79] Remove yet another redundant guard --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 574d18de0e..a741a7a005 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -70,13 +70,7 @@ namespace osu.Game.Overlays.Mods public string SearchTerm { get => SearchTextBox.Current.Value; - set - { - if (SearchTextBox.Current.Value == value) - return; - - SearchTextBox.Current.Value = value; - } + set => SearchTextBox.Current.Value = value; } public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; From a49af06e883d4270cca6061c8c8d8864be6283af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:34:33 +0200 Subject: [PATCH 72/79] Reword comments in `ModSelectOverlay` --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a741a7a005..b12b1a7df4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -247,7 +247,7 @@ namespace osu.Game.Overlays.Mods { base.Hide(); - //We want to clear search for next user interaction with mod overlay + // clear search for next user interaction with mod overlay SearchTextBox.Current.Value = string.Empty; } @@ -615,7 +615,9 @@ namespace osu.Game.Overlays.Mods hideOverlay(true); return true; - //This is handled locally here to prevent search box from coupling in DeselectAllModsButton + // This is handled locally here due to conflicts in input handling between the search text box and the deselect all mods button. + // Attempting to handle this action locally in both places leads to a possible scenario + // wherein activating the binding will both change the contents of the search text box and deselect all mods. case GlobalAction.DeselectAllMods: { if (!SearchTextBox.HasFocus) @@ -664,7 +666,9 @@ namespace osu.Game.Overlays.Mods /// /// - /// This is handled locally here to allow handle first + /// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button. + /// Attempting to handle this action locally in both places leads to a possible scenario + /// wherein activating the "select all" platform binding will both select all text in the search box and select all mods. /// > public bool OnPressed(KeyBindingPressEvent e) { @@ -832,8 +836,7 @@ namespace osu.Game.Overlays.Mods if (!Active.Value) RequestScroll?.Invoke(this); - //By doing this we kill the focus on SearchTextBox. - //Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. + // Killing focus is done here because it's the only feasible place on ModSelectOverlay you can click on without triggering any action. Scheduler.Add(() => GetContainingInputManager().ChangeFocus(null)); return true; From 0b6c0592e4ee8ab28babdcc3384135e86b3d2145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:45:43 +0200 Subject: [PATCH 73/79] Add failing test case for mod preset filtering not working after ruleset change --- .../UserInterface/TestSceneModPresetColumn.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index 3efdba8754..2d54a4e566 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -392,6 +392,28 @@ namespace osu.Game.Tests.Visual.UserInterface new HashSet(this.ChildrenOfType().First().Preset.Value.Mods).SetEquals(mods)); } + [Test] + public void TestTextFiltering() + { + ModPresetColumn modPresetColumn = null!; + + AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); + AddStep("create content", () => Child = modPresetColumn = new ModPresetColumn + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded); + + AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); + AddStep("set text filter", () => modPresetColumn.SearchTerm = "First"); + AddUntilStep("one panel visible", () => modPresetColumn.ChildrenOfType().Count(panel => panel.IsPresent), () => Is.EqualTo(1)); + + AddStep("set mania ruleset", () => Ruleset.Value = rulesets.GetRuleset(3)); + AddUntilStep("no panels visible", () => modPresetColumn.ChildrenOfType().Count(panel => panel.IsPresent), () => Is.EqualTo(0)); + } + private ICollection createTestPresets() => new[] { new ModPreset From d4c9eb013e22290fe0b2998abdbf69073aee281e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 14:51:50 +0200 Subject: [PATCH 74/79] Fix bugged initial state of matching filter flag Was preventing mod preset panels from refiltering correctly on ruleset change due to the `matchingFilter == value` guard. --- osu.Game/Overlays/Mods/ModSelectPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs index cc13657c04..a69fb19c4c 100644 --- a/osu.Game/Overlays/Mods/ModSelectPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -286,7 +286,7 @@ namespace osu.Game.Overlays.Mods public abstract IEnumerable FilterTerms { get; } - private bool matchingFilter; + private bool matchingFilter = true; public virtual bool MatchingFilter { From 75300ca2295bce92b70e23450a1b1ffe61efa221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 15:50:25 +0200 Subject: [PATCH 75/79] Switch search box to initially unfocused Done primarily to keep mod hotkeys working without any behavioural changes when mod select is opened. --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 10 +++++----- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 868ee2c73c..d566a04261 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -550,12 +550,12 @@ namespace osu.Game.Tests.Visual.UserInterface { createScreen(); - AddStep("click on mod column", navigateAndClick); - AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); - AddStep("click on search", navigateAndClick); AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + AddStep("click on mod column", navigateAndClick); + AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + void navigateAndClick() where T : Drawable { InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().FirstOrDefault()); @@ -571,10 +571,10 @@ namespace osu.Game.Tests.Visual.UserInterface const Key focus_switch_key = Key.Tab; AddStep("press tab", () => InputManager.Key(focus_switch_key)); - AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); + AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); AddStep("press tab", () => InputManager.Key(focus_switch_key)); - AddAssert("focused", () => modSelectOverlay.SearchTextBox.HasFocus); + AddAssert("lost focus", () => !modSelectOverlay.SearchTextBox.HasFocus); } [Test] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b12b1a7df4..23e278e378 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -500,8 +500,6 @@ namespace osu.Game.Overlays.Mods base.PopIn(); - SearchTextBox.TakeFocus(); - aboveColumnsContent .FadeIn(fade_in_duration, Easing.OutQuint) .MoveToY(0, fade_in_duration, Easing.OutQuint); From b87acfa66feb51787b9a46a207d84af762f82865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 15:53:49 +0200 Subject: [PATCH 76/79] Dynamically change placeholder to convey how to activate search --- osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 7 +++++++ osu.Game/Localisation/ModSelectOverlayStrings.cs | 9 +++++++-- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index a6954fafb1..fb0a66cb8d 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -37,6 +38,12 @@ namespace osu.Game.Graphics.UserInterface set => textBox.HoldFocus = value; } + public LocalisableString PlaceholderText + { + get => textBox.PlaceholderText; + set => textBox.PlaceholderText = value; + } + public new bool HasFocus => textBox.HasFocus; public void TakeFocus() => textBox.TakeFocus(); diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index f11c52ee20..05dcf138d7 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.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 osu.Framework.Localisation; @@ -39,6 +39,11 @@ namespace osu.Game.Localisation /// public static LocalisableString UseCurrentMods => new TranslatableString(getKey(@"use_current_mods"), @"Use current mods"); + /// + /// "tab to search..." + /// + public static LocalisableString TabToSearch => new TranslatableString(getKey(@"tab_to_search"), @"tab to search..."); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 23e278e378..2f39758982 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -303,6 +303,13 @@ namespace osu.Game.Overlays.Mods }); } + protected override void Update() + { + base.Update(); + + SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? Resources.Localisation.Web.CommonStrings.InputSearch : ModSelectOverlayStrings.TabToSearch; + } + /// /// Select all visible mods in all columns. /// From b4c1266fc5ca746cb0ea0c50b0093bc74796038b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Jun 2023 15:56:15 +0200 Subject: [PATCH 77/79] Add TODO for future support of typical search shortcuts --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 2f39758982..9035503723 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -693,6 +693,7 @@ namespace osu.Game.Overlays.Mods if (e.Repeat || e.Key != Key.Tab) return false; + // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) if (SearchTextBox.HasFocus) SearchTextBox.KillFocus(); else From 425d3c23f5265b136c9a385304897db208b9b6b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jun 2023 01:02:16 +0900 Subject: [PATCH 78/79] Fix some code layout and NRT some classes --- osu.Game/Overlays/Mods/ModState.cs | 2 -- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 2 -- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 13 +++++-------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModState.cs b/osu.Game/Overlays/Mods/ModState.cs index 1ec517ca11..7a5bc0f3ae 100644 --- a/osu.Game/Overlays/Mods/ModState.cs +++ b/osu.Game/Overlays/Mods/ModState.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Game.Rulesets.Mods; diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index dd14514a3b..bb61cdc35d 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index d5e57b9ec9..4d5d724089 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Overlays; using System.Collections.Generic; @@ -36,11 +34,10 @@ namespace osu.Game.Screens.OnlinePlay protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons() - .Prepend( - SelectAllModsButton = new SelectAllModsButton(this) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); + .Prepend(SelectAllModsButton = new SelectAllModsButton(this) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); } } From db445660e763b510ce4a7214e9f8614045a87546 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Jun 2023 01:06:45 +0900 Subject: [PATCH 79/79] Avoid resolving realm `Live` more than once --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 607d236781..00f6e36972 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -88,10 +88,12 @@ namespace osu.Game.Overlays.Mods private IEnumerable getFilterTerms() { - yield return Preset.Value.Name; - yield return Preset.Value.Description; + var preset = Preset.Value; - foreach (Mod mod in Preset.Value.Mods) + yield return preset.Name; + yield return preset.Description; + + foreach (Mod mod in preset.Mods) { yield return mod.Name; yield return mod.Acronym;